I have a core dump of an executable that was NOT built with debug symbols.
Can I recover argv contents to see what the command line was?
If I run gdb, I can see a backtrace, and I can navigate to the main() frame. Once there, is there a way to recover argv, without knowing its exact address?
I am on x86_x64 (Intel Xeon CPU) running a CEntOS Linux distro/kernel,
One reason I am hopeful is that the core dump seems to show a partial argv.
(The program is postgres, and when I load the core file, gdb prints a message that includes the postgres db-user name, client OP address, and first 10 characters of the query))
Try running a "pmap" against the core file (if hp/ux has this tool). This should report the starting addresses of all modules in the core file. With this info, you should be able to take the address of the failure location and figure out what library crashed.
Select Run | Open Core Dump from the main menu or call this action from Help | Find Action ( Ctrl+Shift+A ). If there are no Core Dump Debug configurations in the project, the Open Core Dump dialog will be shown right away. Otherwise, select New Core Dump from the popup menu.
With a core file, we can use the debugger (GDB) to inspect the state of the process at the moment it was terminated and to identify the line of code that caused the problem. That's a situation where a core dump file could be produced, but it's not by default.
On x86_64
the arguments are passed in %rdi
, %rsi
, etc. registers (calling convention).
Therefore, when you step into the main
frame, you should be able to:
(gdb) p $rdi # == argc
(gdb) p (char**) $rsi # == argv
(gdb) set $argv = (char**)$rsi
(gdb) set $i = 0
(gdb) while $argv[$i]
> print $argv[$i++]
> end
Unfortunately, GDB will not normally restore $rdi
and $rsi
when you switch frames. So this example doesn't work:
cat t.c
#include <stdlib.h>
int bar() { abort(); }
int foo() { return bar(); }
int main()
{
foo();
return 0;
}
gcc t.c && ./a.out
Aborted (core dumped)
gdb -q ./a.out core
Core was generated by `./a.out'.
Program terminated with signal 6, Aborted.
#0 0x00007fdc8284aa75 in *__GI_raise (sig=<optimized out>) at ../nptl/sysdeps/unix/sysv/linux/raise.c:64
64 ../nptl/sysdeps/unix/sysv/linux/raise.c: No such file or directory.
in ../nptl/sysdeps/unix/sysv/linux/raise.c
(gdb) bt
#0 0x00007fdc8284aa75 in *__GI_raise (sig=<optimized out>) at ../nptl/sysdeps/unix/sysv/linux/raise.c:64
#1 0x00007fdc8284e5c0 in *__GI_abort () at abort.c:92
#2 0x000000000040052d in bar ()
#3 0x000000000040053b in foo ()
#4 0x000000000040054b in main ()
(gdb) fr 4
#4 0x000000000040054b in main ()
(gdb) p $rdi
$1 = 5524 ### clearly not the right value
So you'll have to work some more ...
What you can do is use the knowledge of how Linux stack is set up at process startup, combined with the fact that GDB will restore stack pointer:
(gdb) set backtrace past-main
(gdb) bt
#0 0x00007ffff7a8da75 in *__GI_raise (sig=<optimized out>) at ../nptl/sysdeps/unix/sysv/linux/raise.c:64
#1 0x00007ffff7a915c0 in *__GI_abort () at abort.c:92
#2 0x000000000040052d in bar ()
#3 0x000000000040053b in foo ()
#4 0x0000000000400556 in main ()
#5 0x00007ffff7a78c4d in __libc_start_main (main=<optimized out>, argc=<optimized out>, ubp_av=<optimized out>, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffdad8) at libc-start.c:226
#6 0x0000000000400469 in _start ()
(gdb) frame 6
(gdb) disas
Dump of assembler code for function _start:
0x0000000000400440 <+0>: xor %ebp,%ebp
0x0000000000400442 <+2>: mov %rdx,%r9
0x0000000000400445 <+5>: pop %rsi
0x0000000000400446 <+6>: mov %rsp,%rdx
0x0000000000400449 <+9>: and $0xfffffffffffffff0,%rsp
0x000000000040044d <+13>: push %rax
0x000000000040044e <+14>: push %rsp
0x000000000040044f <+15>: mov $0x400560,%r8
0x0000000000400456 <+22>: mov $0x400570,%rcx
0x000000000040045d <+29>: mov $0x40053d,%rdi
0x0000000000400464 <+36>: callq 0x400428 <__libc_start_main@plt>
=> 0x0000000000400469 <+41>: hlt
0x000000000040046a <+42>: nop
0x000000000040046b <+43>: nop
End of assembler dump.
So now we expect the original %rsp
to be $rsp+8
(one POP, two PUSHes), but it could be at $rsp+16
due to alignment that was done at instruction 0x0000000000400449
Let's see what's there ...
(gdb) x/8gx $rsp+8
0x7fffbe5d5e98: 0x000000000000001c 0x0000000000000004
0x7fffbe5d5ea8: 0x00007fffbe5d6eb8 0x00007fffbe5d6ec0
0x7fffbe5d5eb8: 0x00007fffbe5d6ec4 0x00007fffbe5d6ec8
0x7fffbe5d5ec8: 0x0000000000000000 0x00007fffbe5d6ecf
That looks promising: 4 (suspected argc), followed by 4 non-NULL pointers, followed by NULL.
Let's see if that pans out:
(gdb) x/s 0x00007fffbe5d6eb8
0x7fffbe5d6eb8: "./a.out"
(gdb) x/s 0x00007fffbe5d6ec0
0x7fffbe5d6ec0: "foo"
(gdb) x/s 0x00007fffbe5d6ec4
0x7fffbe5d6ec4: "bar"
(gdb) x/s 0x00007fffbe5d6ec8
0x7fffbe5d6ec8: "bazzzz"
Indeed, that's how I invoked the binary. As a final sanity check, does 0x00007fffbe5d6ecf
look like part of the enovironment?
(gdb) x/s 0x00007fffbe5d6f3f
0x7fffbe5d6f3f: "SSH_AGENT_PID=2874"
Yep, that's the beginning (or the end) of the environment.
So there you have it.
Final notes: if GDB didn't print <optimized out>
so much, we could have recovered argc
and argv
from frame #5. There is work on both GDB and GCC sides to make GDB print much less of "optimized out" ...
Also, when loading the core, my GDB prints:
Core was generated by `./a.out foo bar bazzzz'.
negating the need for this whole exercise. However, that only works for short command lines, while the solution above will work for any command line.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With