Reading /proc/pid/mem from ptraced process returns EOF - linux

Context
I've been working in a program for my final assignment and I've found the following strange behaviour.
I've coded a tracer in order to be able to read/write memory from child processes. My intention is to read the currently executed instruction at a given point, then disassemble it in order to get some information about memory operands and such.
For testing purposes a simple HelloWorld written in C is used.
Information
The code of the tracer I've written, albeit simplified in order to be more understandable, is this:
size_t tracer::readMem(ADDR_t offset, char *buff, size_t len) {
REQUIRE(_state != TRCS_UNINITIALISED);
if (_memsdescr < 0 || fcntl(_memsdescr, F_GETFL) < 0) {
_memsdescr = open(("/proc/" + to_string(_child_pid_t) + "/mem").c_str(), O_LARGEFILE);
if (_memsdescr < 0) {
logmanager::getInstance ().emplaceBasicLogger ("tracer")
.log ( SLVL_ERROR, "Process\' memory could not be "
" opened. \n");
PANIC;
} else {
logmanager::getInstance ().emplaceBasicLogger ("tracer")
.logfmt ( SLVL_DEBUG, "Opened process' memory. %lx bytes long\n",
lseek(_memsdescr, 0, SEEK_END));
}
}
ASSERT(offset <= lseek(_memsdescr, 0, SEEK_END));
int ret = pread(_memsdescr, buff, len, offset);
if (ret < 0) {
logmanager::getInstance ().emplaceBasicLogger ("tracer")
.logfmt( SLVL_ERROR, "Error reading from memory descriptor: %s\n", sys_errlist[errno]);
}
return ret;
}
The code that controls the exectution is the following. Basically all it does is to read 15 bytes chunks from /proc/mem. The address of those chunks is obtained by getting the value of the RIP (instruction pointer) after a call to ptrace(PTRACE_SINGLESTEP). This means all the memory I try to read should be mapped in the process' memory space.
trc.load (filename);
trc.launchProgram();
cout << " Started with pid " << trc.getChildPid() << endl << endl;
//memspacy::memory_map::printSystemProcVmap(trc.getChildPid());
//inj.prop_setTraceSyscalls (true);
while (trc.prop_getState () != memspacy::TRCS_STOPPED) {
//if (trc.isSyscall()){
// trc.showSyscall();
//}
//HERE IS WHERE THE DISASSEMBLY takes place
if (trc.readMem(trc.peekReg(a_RIP), inst_buff, MAX_ARCH_INST_LEN)
&& dec.disassemble()) {
dec.printFormatted();
}
trc.singleStep();
}
Issue
The HelloWorld should be comprised of several thousand instructions, but the output I get is this.
mov %rsp, %rdi
add %al, (%rax)
push %rdi
push %rsi
push %rsp
mov %edi, %ebx
in %dx, %al
xor %ecx, -0x3f(%rax)
invalid
Useful facts
It seems that after a couple instructions the read function stops getting any data al all. No error is thrown, the only problem is that reading the memory returns 0 bytes. This would mean that the EOF is reached as per the information in the read() manpage, but lseek() returns a size of 0xFFFFFFFFFFFF, so there should be no problem on that end. Aso, all the reads fall within the mapped regions since I'm using the program counter as the offset.
I can not really think of any thing other than page permissions, but they all have read permissions set, otherwise it wouldn't even execute. The process is properly Ptraced, and the execution runs just fine, with the expected behaviour, even the registers are the exactly the same as in the control test (a test used to check the original behaviour).
My current guess is that at some point it reaches the end of a mapped region and that renders the descriptor useless, hence that "invalid" instruction at the end, but even opening the file on each read the result does not change.
Data
Here is the memory map and the read offset of the last valid read.
00400000-00401000 r-xp 00000000 08:06 7602542 /home/amontes/workspace/memspacy_build/assets/test/test
00600000-00602000 rw-p 00000000 08:06 7602542 /home/amontes/workspace/memspacy_build/assets/test/test
**7fe3eb602000-7fe3eb625000 r-xp 00000000 08:11 657171 /lib/x86_64-linux-gnu/ld-2.19.so**
7fe3eb824000-7fe3eb826000 rw-p 00022000 08:11 657171 /lib/x86_64-linux-gnu/ld-2.19.so
7fe3eb826000-7fe3eb827000 rw-p 00000000 00:00 0
7fff57783000-7fff577a4000 rw-p 00000000 00:00 0 [stack]
7fff577fe000-7fff57800000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
Last valid offset 7fe3eb606a7c -> This shows the invalid instruction
First invalid offset 7fe3eb606a7d -> This returns EOF
Any help or any idea would be really appreciated. Thank you.

Well, I don't know if it was a bug caused by some update, or a very specific kernel version or whatever you want to call it. After a clean install of the OS, everything works correctly. I can get the instruction stream, and the the read function always returns data.
Before wiping the HD and prior to the installation, I tried ptrace(PTRACE_PEEKDATA) without luck. Now everything works as it should.
I don't really believe this question is going to help anybody, but sometimes a clean start is the way to go. As much as I hate to admit it, it's happened to me from time to time, not always related to coding software.

Related

Where are "str" values allocated? Is it in the heap?

I am new to Rust and I try to figure out where things are allocated.
I use Rust 1.64.0 on Ubuntu 22.04.0 (jammy):
$ rustc --version
rustc 1.64.0 (a55dd71d5 2022-09-19)
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 22.04.1 LTS
Release: 22.04
Codename: jammy
I try to explore the executable produce by compiling this very basic Rust program:
fn tester() {
let borrowed_string: &str = "world";
println!("{}", borrowed_string);
}
fn main() { tester(); }
Compilation (OK, this is overkill... but I want to be sure that I have all I need for using GDB):
$ cargo build --config "profile.dev.debug=true" --config "profile.dev.opt-level=0" --profile=dev
Compiling variables v0.1.0 (/home/denis/Documents/github/rust-playground/variables)
Finished dev [unoptimized + debuginfo] target(s) in 0.71s
Run rust-gdb, set the brakpoint and run the program:
$ rust-gdb target/debug/variables
GNU gdb (Ubuntu 12.0.90-0ubuntu1) 12.0.90
...
Reading symbols from target/debug/variables...
(gdb) b 3
Breakpoint 1 at 0x7959: file src/main.rs, line 3.
(gdb) r
Starting program: /home/denis/Documents/github/rust-playground/variables/target/debug/variables
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Breakpoint 1, variables::tester () at src/main.rs:3
3 println!("{}", borrowed_string);
(gdb)
s
Print the memory mapping;
(gdb) info proc map
process 6985
Mapped address spaces:
Start Addr End Addr Size Offset Perms objfile
0x555555554000 0x55555555a000 0x6000 0x0 r--p /home/denis/Documents/github/rust-playground/variables/target/debug/variables
0x55555555a000 0x555555591000 0x37000 0x6000 r-xp /home/denis/Documents/github/rust-playground/variables/target/debug/variables
0x555555591000 0x55555559f000 0xe000 0x3d000 r--p /home/denis/Documents/github/rust-playground/variables/target/debug/variables
0x5555555a0000 0x5555555a3000 0x3000 0x4b000 r--p /home/denis/Documents/github/rust-playground/variables/target/debug/variables
0x5555555a3000 0x5555555a4000 0x1000 0x4e000 rw-p /home/denis/Documents/github/rust-playground/variables/target/debug/variables
0x5555555a4000 0x5555555c5000 0x21000 0x0 rw-p [heap]
0x7ffff7d5c000 0x7ffff7d5f000 0x3000 0x0 rw-p
0x7ffff7d5f000 0x7ffff7d87000 0x28000 0x0 r--p /usr/lib/x86_64-linux-gnu/libc.so.6
0x7ffff7d87000 0x7ffff7f1c000 0x195000 0x28000 r-xp /usr/lib/x86_64-linux-gnu/libc.so.6
0x7ffff7f1c000 0x7ffff7f74000 0x58000 0x1bd000 r--p /usr/lib/x86_64-linux-gnu/libc.so.6
0x7ffff7f74000 0x7ffff7f78000 0x4000 0x214000 r--p /usr/lib/x86_64-linux-gnu/libc.so.6
0x7ffff7f78000 0x7ffff7f7a000 0x2000 0x218000 rw-p /usr/lib/x86_64-linux-gnu/libc.so.6
0x7ffff7f7a000 0x7ffff7f87000 0xd000 0x0 rw-p
0x7ffff7f87000 0x7ffff7f8a000 0x3000 0x0 r--p /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
0x7ffff7f8a000 0x7ffff7fa1000 0x17000 0x3000 r-xp /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
0x7ffff7fa1000 0x7ffff7fa5000 0x4000 0x1a000 r--p /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
0x7ffff7fa5000 0x7ffff7fa6000 0x1000 0x1d000 r--p /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
0x7ffff7fa6000 0x7ffff7fa7000 0x1000 0x1e000 rw-p /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
0x7ffff7fb8000 0x7ffff7fb9000 0x1000 0x0 ---p
0x7ffff7fb9000 0x7ffff7fbd000 0x4000 0x0 rw-p
0x7ffff7fbd000 0x7ffff7fc1000 0x4000 0x0 r--p [vvar]
0x7ffff7fc1000 0x7ffff7fc3000 0x2000 0x0 r-xp [vdso]
0x7ffff7fc3000 0x7ffff7fc5000 0x2000 0x0 r--p /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
0x7ffff7fc5000 0x7ffff7fef000 0x2a000 0x2000 r-xp /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
0x7ffff7fef000 0x7ffff7ffa000 0xb000 0x2c000 r--p /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
0x7ffff7ffb000 0x7ffff7ffd000 0x2000 0x37000 r--p /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
0x7ffff7ffd000 0x7ffff7fff000 0x2000 0x39000 rw-p /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
0x7ffffffde000 0x7ffffffff000 0x21000 0x0 rw-p [stack]
0xffffffffff600000 0xffffffffff601000 0x1000 0x0 --xp [vsyscall]
(gdb)
We have:
HEAP: [0x5555555A4000, 0x5555555C4FFF]
STACK: [0x7FFFFFFDE000, 0x7FFFFFFFEFFF]
I assume that the data "world" is allocated in the heap. Thus, let's find out... but look at what I obtain!!!
(gdb) find 0x5555555A4000, 0x5555555C4FFF, "world"
0x5555555a4ba0
1 pattern found.
(gdb) find 0x5555555A4000, 0x5555555C4FFF, "world"
0x5555555a4be0
1 pattern found.
(gdb) find 0x5555555A4000, 0x5555555C4FFF, "world"
0x5555555a4c20
1 pattern found.
(gdb) find 0x5555555A4000, 0x5555555C4FFF, "world"
0x5555555a4c60
1 pattern found.
(gdb) find 0x5555555A4000, 0x5555555C4FFF, "world"
0x5555555a4ca0
1 pattern found.
(gdb)
Please note that:
0x5555555A4BA0 -> 0x5555555A4BE0 => 64
0x5555555A4BE0 -> 0x5555555A4C20 => 64
0x5555555A4C20 -> 0x5555555A4C60 => 64
Question: why does the position of the data change from one scan to the next?
(This question may not be related specifically to Rust.)
(gdb) p borrowed_string
$1 = "world"
(gdb) ptype borrowed_string
type = struct &str {
data_ptr: *mut u8,
length: usize,
}
(gdb) p borrowed_string.data_ptr
$2 = (*mut u8) 0x555555591000
(gdb) x/5c 0x555555591000
0x555555591000: 119 'w' 111 'o' 114 'r' 108 'l' 100 'd'
We have:
0x5555555A4000: start of the heap (included)
0x5555555C5000: end of the heap (excluded), and start of the "uncharted" memory territory
0x555555591000: address of the data "world"
0x7FFFF7D5F000: end of the "uncharted" memory territory
Question: what is this "uncharted" memory territory between 0x5555555C5000 (included) and 0x7FFFF7D5F000 (excluded)?
EDIT:
As mentioned in the comments below, I made a mistake. The data "world" is "before" the heap... The comments make sense. Thank you.
Question: what is this "uncharted" memory territory between 0x5555555C5000 (included) and 0x7FFFF7D5F000 (excluded)?
It's nothing. It's memory which is not mapped.
Where are "str" values allocated? Is it in the heap?
Your memory map answers the question:
0x555555591000 0x55555559f000 0xe000 0x3d000 r--p /home/denis/Documents/github/rust-playground/variables/target/debug/variables
This is one of the areas where the binary itself is mapped. Most likely the "rodata" segment of the executable. String literals are generally "allocated" in the binary itself, then a static reference is loaded.
More generally str values are not allocated, they're a reference to data living somewhere else which could be on the heap (if the reference was obtained from a String or Box<str> or some pointers), in the binary (literals, static includes), or even on the stack (e.g. you can create an array on the stack then pass it through std::str::from_utf8 and get an &str out of it)
We have: HEAP: [0x5555555A4000, 0x5555555C4FFF]
That's... not really helpful or useful or true, frankly: modern systems have largely moved away from linear heaps and to memory map heaps. While Linux software still makes some limited use of (s)brk, BSDs qualify them as "historical curiosities". If you assume the heap is contained within these bounds you're going to face lots of surprises.
Question: why does the position of the data change from one scan to the next?
Because the debugger is allocating stuff in context of the program, I assume? So you might be finding the data gdb itself is storing.

Why I can see the several same segments in the /proc/pid/maps output?

Test is on the 32 bit Linux
The code is as below:
int foo(int a, int b)
{
int c = a + b;
return c;
}
int main()
{
int e = 0;
int d = foo(1, 2);
printf("%d\n", d);
scanf("%d", &e);
return 0;
}
and when I use cat /proc/pid/maps to see the memory layout, it seems that I can see three
text segment for my code and the library.
ubuntu% cat /proc/2191/maps
08048000-08049000 r-xp 00000000 08:01 1467306 /home/shuai/work/asm/test1
08049000-0804a000 r--p 00000000 08:01 1467306 /home/shuai/work/asm/test1
0804a000-0804b000 rw-p 00001000 08:01 1467306 /home/shuai/work/asm/test1
09137000-09158000 rw-p 00000000 00:00 0 [heap]
b75c6000-b75c7000 rw-p 00000000 00:00 0
b75c7000-b776b000 r-xp 00000000 08:01 3149924 /lib/i386-linux-gnu/libc-2.15.so
b776b000-b776d000 r--p 001a4000 08:01 3149924 /lib/i386-linux-gnu/libc-2.15.so
b776d000-b776e000 rw-p 001a6000 08:01 3149924 /lib/i386-linux-gnu/libc-2.15.so
b776e000-b7771000 rw-p 00000000 00:00 0
b7780000-b7784000 rw-p 00000000 00:00 0
b7784000-b7785000 r-xp 00000000 00:00 0 [vdso]
b7785000-b77a5000 r-xp 00000000 08:01 3149914 /lib/i386-linux-gnu/ld-2.15.so
b77a5000-b77a6000 r--p 0001f000 08:01 3149914 /lib/i386-linux-gnu/ld-2.15.so
b77a6000-b77a7000 rw-p 00020000 08:01 3149914 /lib/i386-linux-gnu/ld-2.15.so
bfd47000-bfd68000 rw-p 00000000 00:00 0 [stack]
Could any one give me some guide about this issue? Thank you a lot!
Please mind the values in columns 3 (starting offset) and 2 (permissions). Really you have the same part mapped twice, in lines 1 and 2 for your binary file, but, in line 3, it's different. It's permitted to map the same file separately multiple times; different systems could skip merging this into one VM map entry, so it could reflect mapping history but not the current state jist.
If you see at library mappings you could easily find the law that any library is mapped separately:
With permission to read and execute: the main code which shouldn't be changed.
With permission to read: constant data area without code allowed.
With permission to read and write: it combines non-constant data area and relocation tables of shared objects.
Having the same starting 4K binary file area mapped twice could be explained with RTLD logic which differs from an arbitrary library logic due to bootstrapping needs. I don't treat it so important, more so it could easily differ on platform specifics.
Note that the three sections for each file have different permissions: read-only, read-write, and read-execute. This is for security: the code section (read-execute) can't be written to through exploits, and the segment that can be written can't be executed.

Return To Libc with Null byte in the addr

I am trying to perform a return to libc format string attack, but the address I want to write to ( 0x0804a000) has a null byte in it!! I have to read in my format string to snprintf so the null byte causes it to malfunction and Segfaults randomly.
buf[70];
snprintf(buf, 80, argv[1]);
printf(buf);
Here is the GDB dump for printf#plt:
(gdb) disassem 0x080483c0
Dump of assembler code for function printf#plt:
0x080483c0 <+0>: jmp *0x804a000
0x080483c6 <+6>: push $0x0
0x080483cb <+11>: jmp 0x80483b0
End of assembler dump.
Does anyone have any ideas?
My current method is running it like this
./program `perl -e 'print "sh;#\x00\xa0\x04\x08%12345x%10$hn"'`
but there is a null byte. I have also tried
./program `perl -e 'print "sh;#\xff\x9f\x04\x08\x00\xa0\x04\x08%12345x%10$hn%12345x%11$hn"'`
but the address before 0x0804a000 has the global offset table, and therefore snprintf Segfaults before even returning the to function that calls it.
A common way out of this is to make some "memory construction" using the stack.
At the end of the construction you need to have somewhere on the stack (let's call that location n and let's say it correspond to the fifth parameter) :
00 a0 04 08
Given that you can start by writing 0x01 (Or whatever you prefer, we are only interested in the null byte) to n-1. The memory at location n will looks like :
00 ?? ?? ??
Then write 0x04a0 into n+1 thus the memory at location n looks like :
00 a0 04 ??
The final step would be to write 0xff08 into n+3.
Once that's done you can use direct parameter accessing to get your address and write a value at the pointed location.
%12345x%5$n
All you have to do is play with $hn and $n and find a way to overlap the data that suit you. I hope you get the idea.

Determine a thread's stack size and location

how can I determine the base of a thread's stack and its size under Linux? Is there an available C/C++ API or a way to find out in gdb?
thanks
Here's a different method of doing this, involving reading /proc/self/maps. Unlike some of the other methods, it doesn't require special instrumentation at the start of your program and gives you an exact position for the end of the stack.
If you try cat /proc/self/maps, you get something like this:
00400000-0040c000 r-xp 00000000 08:01 6039736 /usr/bin/cat
0060b000-0060c000 r--p 0000b000 08:01 6039736 /usr/bin/cat
0060c000-0060d000 rw-p 0000c000 08:01 6039736 /usr/bin/cat
00908000-00929000 rw-p 00000000 00:00 0 [heap]
7fcdb1c68000-7fcdb1e01000 r-xp 00000000 08:01 6032628 /usr/lib/libc-2.21.so
7fcdb1e01000-7fcdb2000000 ---p 00199000 08:01 6032628 /usr/lib/libc-2.21.so
7fcdb2000000-7fcdb2004000 r--p 00198000 08:01 6032628 /usr/lib/libc-2.21.so
7fcdb2004000-7fcdb2006000 rw-p 0019c000 08:01 6032628 /usr/lib/libc-2.21.so
7fcdb2006000-7fcdb200a000 rw-p 00000000 00:00 0
7fcdb200a000-7fcdb202c000 r-xp 00000000 08:01 6032717 /usr/lib/ld-2.21.so
7fcdb21f5000-7fcdb21f8000 rw-p 00000000 00:00 0
7fcdb2209000-7fcdb222b000 rw-p 00000000 00:00 0
7fcdb222b000-7fcdb222c000 r--p 00021000 08:01 6032717 /usr/lib/ld-2.21.so
7fcdb222c000-7fcdb222d000 rw-p 00022000 08:01 6032717 /usr/lib/ld-2.21.so
7fcdb222d000-7fcdb222e000 rw-p 00000000 00:00 0
7ffe78c41000-7ffe78c62000 rw-p 00000000 00:00 0 [stack]
7ffe78dba000-7ffe78dbc000 r--p 00000000 00:00 0 [vvar]
7ffe78dbc000-7ffe78dbe000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
As you can see, there is a [stack] entry. This is probably what you're looking for.
An example program to parse this line out:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[]) {
FILE *file = fopen("/proc/self/maps", "r");
char line[1024];
void *result = NULL;
while (!feof(file)) {
if (fgets(line, sizeof(line) / sizeof(char), file) == NULL) {
break;
}
unsigned long start, end, offset;
unsigned int devma, devmi, ino;
char perms[6];
char path[128];
if (sscanf(line, "%lx-%lx %5s %lx %d:%d %d %127s", &start, &end, &perms, &offset, &devma, &devmi, &ino, &path) != 8) {
continue; // could not parse. fail gracefully and try again on the next line.
}
if (strcmp(path, "[stack]") == 0) { // use [stack:TID] for a thread besides the main thread
printf("Stack found from %lx to %lx\n", start, end);
break;
}
}
fclose(file);
return 0;
}
This will print something like:
Stack found from 7fff91834000 to 7fff91855000
This is probably fairly close to what you're looking for.
If you're okay with not getting the exact top of the stack (which might be enough for some applications), traversing the frame pointers could work:
// main.c
#include <stdint.h>
// -fno-omit-frame-pointer might be required - otherwise you might get a crash
// might not be exactly at the top; but probably very close
void *stack_top() {
void **top;
asm("movq %%rbp, %0" : "=r" (top)); // set top to %rbp - replace with %ebp for 32-bit x86
// if top is higher in memory than the variable, then still part of the stack.
while ((uintptr_t) *top > (uintptr_t) &top) {
top = *top;
}
return top;
}
This works because the %rbp register (or %ebp register on 32-bit) is used to store the pointer to the base of the parent stack frame (where the saved %rbp or %ebp value is) - so we can iteratively traverse this linked list until we reach an invalid address.
Note that stack_top might fail in some cases relating to the &top comparison - my system terminated the linked list with a pointer to a location in the program loading code, so this was the best way that I found to detect this - but you'll want to test this thoroughly. (If anyone has a better way to detect the end of the chain, please add a comment.)
Example test program:
#include <stdio.h>
#include <pthread.h>
void *test_another_layer(int x) {
return stack_top();
}
void *subthread(void *ptr) {
void *st1 = stack_top();
void *st2 = test_another_layer(0);
void *st3 = &ptr;
printf("stack tops 2: %x %x %x\n", st1, st2, st3);
return NULL;
}
int main(int argc, char *argv[]) {
void *st1 = stack_top();
void *st2 = test_another_layer(0);
void *st3 = &argc;
printf("stack tops: %x %x %x\n", st1, st2, st3);
pthread_t ot;
if (pthread_create(&ot, NULL, subthread, NULL) != 0) {
perror("cannot create");
return 1;
}
if (pthread_join(ot, NULL) != 0) {
perror("cannot join");
return 2;
}
return 0;
}

How to generate the backtrace by looking at the stack values?

Quiz : How to generate the backtrace by looking at the stack values?
0xf3e2de34 f3e2de70 c0135351 401ef021 00000000 p.bsQS...p......
0xf3e2de44 f3e2de81 00000021 f3e2c000 f7950924 ..bs......bs...w
0xf3e2de54 00000000 401ef000 00000246 00000246 .....p..F...F...
0xf3e2de64 00000001 00000001 f7950000 f3e2df18 ...........w..bs
0xf3e2de74 c02898b5 322d7875 23203235 00007820 5...ux.252...x..
0xf3e2de84 f3e2de98 00000000 f7950bd0 bffff0ec ..bs....P..wlp..
0xf3e2de94 c70cf660 00000000 00000000 bffff0ec .v.G........lp..
0xf3e2dea4 f3e2c000 f7950930 7fffffff 00000000 ..bs0..w........
0xf3e2deb4 00000000 00000001 00000000 bffff07b .............p..
0xf3e2dec4 f5cfd880 bffff07b 00000000 f5f24740 .XOu.p.......Gru
0xf3e2ded4 c0124f87 00000000 00000000 00200200 .O..............
0xf3e2dee4 f3e2defc 067b3067 00000000 f5f24740 ..bsg0.......Gru
0xf3e2def4 c0124f87 f7950934 f7950934 c028331b .O..4..w4..w.3..
0xf3e2df04 00000000 c01b0fe8 f5cfd880 f7950000 ....h....XOu...w
0xf3e2df14 fffffffb f3e2df50 c0283642 00000001 ....P.bsB6......
0xf3e2df24 00000001 00000001 f3e2c000 f6fe30d0 ..........bsP0.v
Hint - the current function can always be determined from EIP
I found this question at kernelnewbies/ABI documentation.
I really dont understand the hint given there?(Maybe because i have no idea about this thing).
Could somebody explain me how to solve these kinda questions.
In this case, you can do it quite reliably, since that code has been compiled with frame pointers enabled.
What you need to know:
EIP is a register that points at the next instruction to execute.
When calling a function, the arguments and then EIP (so the called function knows where to return to) are saved on the stack.
When the compiler has been told (explicitly or implicitly) to use frame pointers, it then saves the frame pointer (in the EBP register) on the stack (so it can later restore the frame pointer to the value it had on the calling function), and then sets the frame pointer to point to the current top of the stack. This allows accessing easily arguments and local variables from a known point of reference (the frame pointer), and greatly simplifies debugging.
Then, space is reserved for local variables, and the function is executed.
When returning from the function, the previous frame pointer and instruction pointer are restored.
So, to generate a backtrace, you would follow the frame pointers, looking at the corresponding saved EIPS. So:
current function was called from c0135351
follow f3e2de70 → was called from c02898b5
follow f3e2df18 → was called from c0283642
Of course, this was the easy case. When you don't have frame pointers, you have to guess whether a given value on the stack corresponds to an instruction pointer.
The missing part is how to translate this numbers to function names. Having an unstripped vmlinux (note the x, not a z) file is invaluable. System.map just contains some symbols, so very often you'll only end up knowing that the relevant function was between function A and function B.
Edit:
A function call on x86 looks something like:
...
int main() add $-0x8,%esp ; alignment
{ push $0x2 ; arg 2
... push $0x1 ; arg 1
func(1, 2); call func ; function call
... add $0x10,%esp ; pop args from stack
} ...
And the called function looks something like:
void func(int arg1, int arg2) push %ebp ;\
{ mov %esp,%ebp ;/ create stack frame
int local1; sub $0x18,%esp ; reserves space
... ...
} mov %ebp,%esp ;\
pop %ebp ;/ destroys frame
ret ; returns
So, the stack will look similar to:
: :
+-----------+
: alignment :
+-----------+
12(%ebp) | arg2 |
+-----------+
8(%ebp) | arg1 |
+-----------+
4(%ebp) | ret | -----> return address
+-----------+
(%ebp) | ebp | -----> previous ebp
+-----------+
-4(%ebp) | local1 | -----> local vars
+-----------+
: alignment :
+-----------+
: :
(Lower addresses are lower on the ASCII-art)
So, if you keep following the saved EBP pointers, you can get the saved EIP pointers (ret above), which point to instructions in the call chain (in the return chain, to be precise).
The EIP is the instruction pointer I think - it holds the address of the instruction being executed at the moment.

Resources