The main point of a crash reporter tool like Google breakpad is to generate core dump or minidump files from stripped binaries to process later with debugging symbols. normally these binaries are release builds with compiler optimizations applied and also stripped.
To reproduce the problem on Linux:
1.Build + install google breakpad:
git clone https://chromium.googlesource.com/breakpad/breakpad && cd breakpad
git clone https://chromium.googlesource.com/linux-syscall-support src/third_party/lss
./configure --prefix=/usr/local
make -j$(nproc) && sudo make install
2.The Code:
CMakeLists.txt:
cmake_minimum_required(VERSION 3.10)
project(BreakPadTest)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_VERBOSE_MAKEFILE TRUE)
set(BREAKPAD_DIR "/usr/local/include/breakpad")
option(OPTION_WITH_O1 "With -O1" OFF)
if(OPTION_WITH_O1)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O1 -fno-omit-frame-pointer")
endif()
include_directories(
${BREAKPAD_DIR}
)
add_executable(${PROJECT_NAME} "main.cc")
target_link_libraries(${PROJECT_NAME}
-lstdc++fs
-pthread
libbreakpad_client.a
)
main.cc:
#include <thread>
#include <experimental/filesystem>
#include <client/linux/handler/exception_handler.h>
namespace breakpad = google_breakpad;
namespace filesystem = std::experimental::filesystem;
static bool DumpCallBack(const breakpad::MinidumpDescriptor& md,
void* context,
bool success) {
(void)md;
(void)context;
return success;
}
static void fault(unsigned after) {
std::this_thread::sleep_for(std::chrono::seconds{after});
delete reinterpret_cast<std::string*>(0xFEE1DEAD);
}
int32_t main(int argc, char** argv) {
(void)argc;
(void)argv;
auto pwd = filesystem::current_path();
const auto dumpDir = pwd.string() + "/dumps";
filesystem::create_directory(dumpDir);
breakpad::MinidumpDescriptor md(dumpDir);
new google_breakpad::ExceptionHandler(
md,
/* FilterCallback */ nullptr,
DumpCallBack,
/* callback_context */ nullptr,
true,
-1
);
fault(1U);
return EXIT_SUCCESS;
}
dump.sh:
#!/bin/bash
#
# e.g ./dump.sh ./exec $PWD/dumps
#
set -e
set -u
DBG_INFO=$(realpath ${1})
DUMPS_DIR=$(realpath ${2:-$PWD/dumps})
DUMP_SYMS=${3:-~/WorkSpace/libraries/breakpad/src/tools/linux/dump_syms/dump_syms}
STAK_WALK=${4:-~/WorkSpace/libraries/breakpad/src/processor/minidump_stackwalk}
#
# Generate debug symbols
#
base=$(basename $DBG_INFO)
$DUMP_SYMS $DBG_INFO > $DUMPS_DIR/$base.sym
#
# Create dump dir structure
#
list=($(head -n1 $DUMPS_DIR/$base.sym))
hash=${list[3]}
mkdir -p $DUMPS_DIR/symbols/$base/$hash
mv $DUMPS_DIR/$base.sym $DUMPS_DIR/symbols/$base/$hash
#
# Produce stack trace
#
RED='\033[0;36m'
NC='\033[0m' # No Color
tree $DUMPS_DIR
for dmp in $DUMPS_DIR/*.dmp ; do
filename=$(basename -- "${dmp}")
filename="${filename%.*}"
echo -e "generating stack trace for -> ${RED}${dmp}${NC}"
$STAK_WALK ${dmp} $DUMPS_DIR/symbols > $DUMPS_DIR/${filename}.txt 2>/dev/null
done
3.Run normal Debug version:
cmake -DCMAKE_BUILD_TYPE=Debug . && make
./BreakPadTest
4.Process minidump generated from stage 3:
./dump.sh ./BreakPadTest ./dumps
stackwalk:
Operating system: Linux
0.0.0 Linux 4.19.0-16-amd64 #1 SMP Debian 4.19.181-1 (2021-03-19) x86_64
CPU: amd64
family 6 model 58 stepping 9
1 CPU
GPU: UNKNOWN
Crash reason: SIGSEGV /SEGV_MAPERR
Crash address: 0xfee1dead
Process uptime: not available
Thread 0 (crashed)
0 BreakPadTest!std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_data() const [basic_string.h : 176 + 0x4]
rax = 0x00000000fee1dead rdx = 0x00007ffcfccfcb90
rcx = 0x00007f5b0cbb4bc1 rbx = 0x0000557d577eb8e0
rsi = 0x00007ffcfccfcb90 rdi = 0x00000000fee1dead
rbp = 0x00007ffcfccfcb50 rsp = 0x00007ffcfccfcb50
r8 = 0x0000000000000000 r9 = 0x0000557d577efaf8
r10 = 0xfffffffffffff60b r11 = 0x0000000000000246
r12 = 0x0000557d56d3d2c0 r13 = 0x00007ffcfccfce50
r14 = 0x0000000000000000 r15 = 0x0000000000000000
rip = 0x0000557d56d3dfda
Found by: given as instruction pointer in context
1 BreakPadTest!std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_is_local() const [basic_string.h : 211 + 0xc]
rbx = 0x0000557d577eb8e0 rbp = 0x00007ffcfccfcb80
rsp = 0x00007ffcfccfcb60 r12 = 0x0000557d56d3d2c0
r13 = 0x00007ffcfccfce50 r14 = 0x0000000000000000
r15 = 0x0000000000000000 rip = 0x0000557d56d3e2c5
Found by: call frame info
2 BreakPadTest!std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_dispose() [basic_string.h : 220 + 0xc]
rbx = 0x0000557d577eb8e0 rbp = 0x00007ffcfccfcba0
rsp = 0x00007ffcfccfcb90 r12 = 0x0000557d56d3d2c0
r13 = 0x00007ffcfccfce50 r14 = 0x0000000000000000
r15 = 0x0000000000000000 rip = 0x0000557d56d3dff8
Found by: call frame info
3 BreakPadTest!std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() [basic_string.h : 657 + 0xc]
rbx = 0x0000557d577eb8e0 rbp = 0x00007ffcfccfcbc0
rsp = 0x00007ffcfccfcbb0 r12 = 0x0000557d56d3d2c0
r13 = 0x00007ffcfccfce50 r14 = 0x0000000000000000
r15 = 0x0000000000000000 rip = 0x0000557d56d3d930
Found by: call frame info
4 BreakPadTest!fault [main.cc : 19 + 0xa]
rbx = 0x0000557d577eb8e0 rbp = 0x00007ffcfccfcbf0
rsp = 0x00007ffcfccfcbd0 r12 = 0x0000557d56d3d2c0
r13 = 0x00007ffcfccfce50 r14 = 0x0000000000000000
r15 = 0x0000000000000000 rip = 0x0000557d56d3d3f0
Found by: call frame info
5 BreakPadTest!main [main.cc : 39 + 0xa]
rbx = 0x0000557d577eb8e0 rbp = 0x00007ffcfccfcd70
rsp = 0x00007ffcfccfcc00 r12 = 0x0000557d56d3d2c0
r13 = 0x00007ffcfccfce50 r14 = 0x0000000000000000
r15 = 0x0000000000000000 rip = 0x0000557d56d3d4fd
Found by: call frame info
6 libc.so.6 + 0x2409b
rbx = 0x0000000000000000 rbp = 0x0000557d56d78b80
rsp = 0x00007ffcfccfcd80 r12 = 0x0000557d56d3d2c0
r13 = 0x00007ffcfccfce50 r14 = 0x0000000000000000
r15 = 0x0000000000000000 rip = 0x00007f5b0ca0609b
Found by: call frame info
7 BreakPadTest!fault [main.cc : 20 + 0x3]
rsp = 0x00007ffcfccfcda0 rip = 0x0000557d56d3d402
Found by: stack scanning
8 ld-linux-x86-64.so.2 + 0xf476
rsp = 0x00007ffcfccfce10 rip = 0x00007f5b0cf1c476
Found by: stack scanning
9 BreakPadTest!_start + 0x2a
rsp = 0x00007ffcfccfce40 rip = 0x0000557d56d3d2ea
Found by: stack scanning
10 0x7ffcfccfce48
rsp = 0x00007ffcfccfce48 rip = 0x00007ffcfccfce48
Found by: call frame info
Loaded modules:
0x557d56d34000 - 0x557d56d78fff BreakPadTest ??? (main)
0x7f5b0c9e2000 - 0x7f5b0cb4bfff libc.so.6 ??? (WARNING: No symbols, libc.so.6, A8A9B91823C5CFE5E5B5D946D605D0920)
0x7f5b0cba3000 - 0x7f5b0cbb7fff libpthread.so.0 ???
0x7f5b0cbc4000 - 0x7f5b0cbd7fff libgcc_s.so.1 ???
0x7f5b0cbde000 - 0x7f5b0cc89fff libm.so.6 ???
0x7f5b0cd61000 - 0x7f5b0ce95fff libstdc++.so.6 ???
0x7f5b0cf0d000 - 0x7f5b0cf2bfff ld-linux-x86-64.so.2 ??? (WARNING: No symbols, ld-linux-x86-64.so.2, 7BFD5DF2BE95A34B86FD71080ACCAE8C0)
0x7ffcfcdc5000 - 0x7ffcfcdc6fff linux-gate.so ???
5.Run stage 3 with -O1:
cmake -DCMAKE_BUILD_TYPE=Debug -DOPTION_WITH_O1=ON . && make
./BreakPadTest
6.Process minidump like stage 4:
stackwalk:
Operating system: Linux
0.0.0 Linux 4.19.0-16-amd64 #1 SMP Debian 4.19.181-1 (2021-03-19) x86_64
CPU: amd64
family 6 model 58 stepping 9
1 CPU
GPU: UNKNOWN
Crash reason: SIGSEGV /SEGV_MAPERR
Crash address: 0xfee1dead
Process uptime: not available
Thread 0 (crashed)
0 BreakPadTest!main [basic_string.h : 176 + 0x0]
rax = 0x0000000000000000 rdx = 0x000055bd46f66a40
rcx = 0x00007f7633ea8bc1 rbx = 0x00007ffde4cc7c40
rsi = 0x00007ffde4cc7c40 rdi = 0x00007ffde4cc7c40
rbp = 0x00007ffde4cc7d90 rsp = 0x00007ffde4cc7c20
r8 = 0x0000000000000000 r9 = 0x000055bd474caaf8
r10 = 0x0000000000000000 r11 = 0x0000000000000246
r12 = 0x000055bd474c64f0 r13 = 0x000055bd474c64f0
r14 = 0x0000000000000000 r15 = 0x0000000000000000
rip = 0x000055bd46f1b8dd
Found by: given as instruction pointer in context
1 libc.so.6 + 0x2409b
rbx = 0x0000000000000000 rbp = 0x000055bd46f555e0
rsp = 0x00007ffde4cc7da0 r12 = 0x000055bd46f1b270
r13 = 0x00007ffde4cc7e70 r14 = 0x0000000000000000
r15 = 0x0000000000000000 rip = 0x00007f7633cfa09b
Found by: call frame info
2 BreakPadTest!DumpCallBack [main.cc : 15 + 0x3]
rsp = 0x00007ffde4cc7dc0 rip = 0x000055bd46f1b358
Found by: stack scanning
3 ld-linux-x86-64.so.2 + 0xf476
rsp = 0x00007ffde4cc7e30 rip = 0x00007f7634210476
Found by: stack scanning
4 BreakPadTest!_start + 0x2a
rsp = 0x00007ffde4cc7e60 rip = 0x000055bd46f1b29a
Found by: stack scanning
5 0x7ffde4cc7e68
rsp = 0x00007ffde4cc7e68 rip = 0x00007ffde4cc7e68
Found by: call frame info
Loaded modules:
0x55bd46f14000 - 0x55bd46f55fff BreakPadTest ??? (main)
0x7f7633cd6000 - 0x7f7633e3ffff libc.so.6 ??? (WARNING: No symbols, libc.so.6, A8A9B91823C5CFE5E5B5D946D605D0920)
0x7f7633e97000 - 0x7f7633eabfff libpthread.so.0 ???
0x7f7633eb8000 - 0x7f7633ecbfff libgcc_s.so.1 ???
0x7f7633ed2000 - 0x7f7633f7dfff libm.so.6 ???
0x7f7634055000 - 0x7f7634189fff libstdc++.so.6 ???
0x7f7634201000 - 0x7f763421ffff ld-linux-x86-64.so.2 ??? (WARNING: No symbols, ld-linux-x86-64.so.2, 7BFD5DF2BE95A34B86FD71080ACCAE8C0)
0x7ffde4d9a000 - 0x7ffde4d9bfff linux-gate.so ???
As we can see correct symbols got disappeared from the stack walk of stage 6.
While in other tools like GDB we have correct trace pointing to right location even with -O1 as in stage 5:
Program received signal SIGSEGV, Segmentation fault.
fault (after=1) at /home/iman/WorkSpace/projects/BreakPadTest/src/main.cc:26
26 delete reinterpret_cast<std::string*>(0xFEE1DEAD);
Or in other tools like backward-cpp :
Stack trace (most recent call last):
#3 Object "", at 0xffffffffffffffff, in
#2 Object "/home/iman/WorkSpace/projects/build-CrashReporter-Desktop_Qt_5_11_3_GCC-Debug/CrashReporter", at 0x55f32a66b579, in _start
#1 Source "/build/glibc-vjB4T1/glibc-2.28/csu/../csu/libc-start.c", line 308, in __libc_start_main [0x7f56288be09a]
#0 | Source "/home/iman/WorkSpace/projects/BreakPadTest/src/main.cc", line 48, in main
| 46: #endif // WITH_BREAKPAD
| 47:
| > 48: fault(1U);
| 49:
| 50: return EXIT_SUCCESS;
| Source "/home/iman/WorkSpace/projects/BreakPadTest/src/main.cc", line 26, in fault
| 24: static void fault(unsigned after) {
| 25: std::this_thread::sleep_for(std::chrono::seconds{after});
| > 26: delete reinterpret_cast<std::string*>(0xFEE1DEAD);
| 27: }
| Source "/usr/include/c++/8/bits/basic_string.h", line 657, in
| 655: */
| 656: ~basic_string()
| > 657: { _M_dispose(); }
| 658:
| 659: /**
| Source "/usr/include/c++/8/bits/basic_string.h", line 220, in
| 218: _M_dispose()
| 219: {
| > 220: if (!_M_is_local())
| 221: _M_destroy(_M_allocated_capacity);
| 222: }
| Source "/usr/include/c++/8/bits/basic_string.h", line 211, in
| 209: bool
| 210: _M_is_local() const
| > 211: { return _M_data() == _M_local_data(); }
| 212:
| 213: // Create & Destroy
Source "/usr/include/c++/8/bits/basic_string.h", line 176, in main [0x55f32a66b66c]
174: pointer
175: _M_data() const
> 176: { return _M_dataplus._M_p; }
177:
178: pointer
179: _M_local_data()
Segmentation fault (Address not mapped to object [0xfee1dead])
Segmentation fault
Any idea or hint?
With the optimisation -O1 almost all std::basic_string functions are inline. These functions are inline because std::basic_string is a template and defined in heder files.
There's an old and active issue with google breakpad processor sub-system to handle inline functions or extract inline functions metadata from DWARF debugging information on linux as discussed in these topics:
https://bugzilla.mozilla.org/show_bug.cgi?id=524410
https://bugzilla.mozilla.org/show_bug.cgi?id=563776
https://bugzilla.mozilla.org/show_bug.cgi?id=1665367
https://bugzilla.mozilla.org/show_bug.cgi?id=1398533
https://bugzilla.mozilla.org/show_bug.cgi?id=1636194
https://groups.google.com/g/google-breakpad-discuss/c/ZQOSZBbdF7U/m/58tpSnM1EBsJ
Which requires modification on breakpad internal symbols representation and stack walker but as a workaround you could generate core dump for GDB and get a backtrace with it, for that to happen there's utility inside breakpad code base(src/tools/linux/md2core) called minidump-2-core, so after building your release binary with debugging information:
$ objcopy --only-keep-debug BreakPadTest BreakPadTest.debug
$ strip BreakPadTest
$ ./BreakPadTest
Segmentation fault
$ minidump-2-core -o dumps/45a855b5-8931-4e7e-5f508496-3fe1cacc.core dumps/45a855b5-8931-4e7e-5f508496-3fe1cacc.dmp
$ gdb -c dumps/45a855b5-8931-4e7e-5f508496-3fe1cacc.core BreakPadTest.debug
Program terminated with signal SIGSEGV, Segmentation fault.
#0 std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_is_local (this=0xfee1dead) at /usr/include/c++/8/bits/basic_string.h:656
656 ~basic_string()
(gdb) backtrace
#0 std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_is_local (this=0xfee1dead) at /usr/include/c++/8/bits/basic_string.h:656
#1 std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_dispose (this=0xfee1dead) at /usr/include/c++/8/bits/basic_string.h:220
#2 std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string (this=0xfee1dead, __in_chrg=<optimized out>)
at /usr/include/c++/8/bits/basic_string.h:657
#3 fault (after=1) at /home/iman/WorkSpace/projects/BreakPadTest/src/main.cc:19
#4 main (argc=<optimized out>, argv=<optimized out>) at /home/iman/WorkSpace/projects/BreakPadTest/src/main.cc:39
(gdb)
Related
I am porting code to modern compilers and unfortunately encountered a subtle segmentation fault when an FFI function was invoked from Rust to C++.
The stacktrace showed that after the transition to C++, the first argument provided from Rust magically disappeared which misled C++ to use wrong arguments.
The code was somewhat private so I cannot post it here, but the assembly showed something interesting:
In GCC-7 (where the code runs without problem), the first several lines of assembly look like:
0x0000000000001119 <+0>: push rbp
0x000000000000111a <+1>: mov rbp,rsp
0x000000000000111d <+4>: push r13
0x000000000000111f <+6>: push r12
0x0000000000001121 <+8>: push rbx
0x0000000000001122 <+9>: sub rsp,0x128
0x0000000000001129 <+16>: mov QWORD PTR [rbp-0x118],rdi
0x0000000000001130 <+23>: mov rax,rsi
0x0000000000001133 <+26>: mov rsi,rdx
0x0000000000001136 <+29>: mov rdx,rsi
0x0000000000001139 <+32>: mov QWORD PTR [rbp-0x130],rax
0x0000000000001140 <+39>: mov QWORD PTR [rbp-0x128],rdx
0x0000000000001147 <+46>: mov QWORD PTR [rbp-0x120],rcx
0x000000000000114e <+53>: mov QWORD PTR [rbp-0x140],r8
0x0000000000001155 <+60>: mov QWORD PTR [rbp-0x138],r9
0x000000000000115c <+67>: lea rax,[rbp-0x110]
0x0000000000001163 <+74>: mov rdi,rax
0x0000000000001166 <+77>: call 0x116b
0x000000000000116b <+82>: mov rax,QWORD PTR [rbp-0x128]
0x0000000000001172 <+89>: mov edx,eax
0x0000000000001174 <+91>: mov rcx,QWORD PTR [rbp-0x130]
0x000000000000117b <+98>: lea rax,[rbp-0x110]
0x0000000000001182 <+105>: mov rsi,rcx
0x0000000000001185 <+108>: mov rdi,rax
0x0000000000001188 <+111>: call 0x118d
With GCC-8/9/10, however, the assembly becomes
0x0000000000001125 <+0>: push rbp
0x0000000000001126 <+1>: mov rbp,rsp
0x0000000000001129 <+4>: push r12
0x000000000000112b <+6>: push rbx
0x000000000000112c <+7>: sub rsp,0x120
0x0000000000001133 <+14>: mov QWORD PTR [rbp-0x108],rdi
0x000000000000113a <+21>: mov QWORD PTR [rbp-0x110],rsi
0x0000000000001141 <+28>: mov QWORD PTR [rbp-0x120],rdx
0x0000000000001148 <+35>: mov QWORD PTR [rbp-0x118],rcx
0x000000000000114f <+42>: mov QWORD PTR [rbp-0x128],r8
0x0000000000001156 <+49>: mov QWORD PTR [rbp-0x130],r9
0x000000000000115d <+56>: lea rax,[rbp-0x100]
0x0000000000001164 <+63>: mov rdi,rax
0x0000000000001167 <+66>: call 0x116c
0x000000000000116c <+71>: mov rax,QWORD PTR [rbp-0x118]
0x0000000000001173 <+78>: mov edx,eax
0x0000000000001175 <+80>: mov rcx,QWORD PTR [rbp-0x120]
0x000000000000117c <+87>: lea rax,[rbp-0x100]
0x0000000000001183 <+94>: mov rsi,rcx
0x0000000000001186 <+97>: mov rdi,rax
0x0000000000001189 <+100>: call 0x118e
So the logic is almost the same the shift amount is changed? I think the behavior is so strange.
The argument list is something like:
struct Opaque {
char _inner[0];
}
struct View {
char * base;
size_t len;
}
ReturnType ffi_function(Opaque*, View, uint64_t, View, uint64_t, uint64_t);
I also dumped the stack by x/-100xg $rbp
0x7fe7a67d41c0: 0x00007fe7a67d41f0 0x0000000014c1b1dc
0x7fe7a67d41d0: 0x000000000000003e 0x0000000000000006
0x7fe7a67d41e0: 0x00007fe7a67d4330 0x00007fe88714b540
0x7fe7a67d41f0: 0x0000000000201000 0x0000000013bd0df1
0x7fe7a67d4200: 0x0000000000000001 0x00007fe7a5e2f4e0
0x7fe7a67d4210: 0x000000000000003e 0x000000000000003b
0x7fe7a67d4220: 0x00007fe7a5e01080 0x00007ffebbd1ddb0
0x7fe7a67d4230: 0x000000001602ff80 0x0000000000000000
0x7fe7a67d4240: 0x0000000000000000 0x0000000000000000
0x7fe7a67d4250: 0x0000000000000000 0x0000000018d59680
0x7fe7a67d4260: 0x0000000018d59680 0x0000000000000000
0x7fe7a67d4270: 0x0000000000000000 0x0000000000000000
0x7fe7a67d4280: 0x00007fe700000000 0x0000000000000000
0x7fe7a67d4290: 0x00007fe7a5e2f4e0 0x00007fe88f1630ed
0x7fe7a67d42a0: 0x00007fe7a67d42f8 0x00007fe7a5e2f4e0
0x7fe7a67d42b0: 0x00007fe7a5e2f4e0 0x00007fe88f0d5f42
0x7fe7a67d42c0: 0x00007fe7a5e2f4e0 0x00007fe7a67d43f0
0x7fe7a67d42d0: 0x00007fe7a67d43f0 0x00007fe88f0ffa04
0x7fe7a67d42e0: 0x0000000000000001 0x0000000000000001
0x7fe7a67d42f0: 0x00007fe7a67d43f0 0x00007fe7a5e2f4e0
0x7fe7a67d4300: 0x00007fe7a5e2f4e0 0x0000000000000001
0x7fe7a67d4310: 0x00007fe7a67d43f0 0x00007fe88f0cf1cf
0x7fe7a67d4320: 0x0000000000000006 0x00007fe88714b540
Now, it seems that 0x00007ffebbd1ddb0 is the correct value for the first argument but C++ read it as 0x00007fe7a5e01080
update:
Right after the callq, before executing push %rbp:
This is what I have got from x/-100xg $rsp ($rsp = 0x7fff0b7d4338)
0x7fff0b7d4108: 0x00007fff0ae01080 0x000000000000003e
0x7fff0b7d4118: 0x0000003e00000060 0x00007fff0b7d4ab8
0x7fff0b7d4128: 0x00007fff0b7d4310 0x00007fff0b7d4310
0x7fff0b7d4138: 0x00007fff00000004 0x00007fff0b7d41b8
0x7fff0b7d4148: 0x00007fff0ae2f480 0x00007fff00000004
0x7fff0b7d4158: 0x00007fff0b7d41b8 0x00007fff0ae2f480
0x7fff0b7d4168: 0x0000000000000004 0x0000000000000000
0x7fff0b7d4178: 0x00007fff0b7d41b8 0x00007fff0ae2f498
0x7fff0b7d4188: 0x0000000000000000 0x00007fff0ae2f498
0x7fff0b7d4198: 0x00007ffff3d8219e 0x00007fff0b7d41b8
0x7fff0b7d41a8: 0x00007ffff3da157f 0x00007fff0ae01080
0x7fff0b7d41b8: 0x000000000000003e 0x000000000000003e
0x7fff0b7d41c8: 0x0000000000000002 0x000000000000003e
0x7fff0b7d41d8: 0x00007ffff5d4326d 0x00007fff0ae01080
0x7fff0b7d41e8: 0x000000000000003e 0x00007fff0b7d41b0
0x7fff0b7d41f8: 0x00007fff0ae01080 0x000000000000003e
0x7fff0b7d4208: 0x000000000000003e 0x0000000000000004
0x7fff0b7d4218: 0x00007fff0ae2f480 0x00007fff0ae2f498
0x7fff0b7d4228: 0x0000000000000004 0x00007fff0ae2f480
0x7fff0b7d4238: 0x00007fff0ae2f498 0x00007fff0ae2f480
0x7fff0b7d4248: 0x0000000000000004 0x0000000000000001
0x7fff0b7d4258: 0x00007fff0b7fe2a8 0x00007fff0ae2f480
0x7fff0b7d4268: 0x0000000000000004 0x00007fff0ae2f498
0x7fff0b7d4278: 0x00007fff0ae2f498 0x00007fff0ae2f4e0
0x7fff0b7d4288: 0x0000000000000000 0x00007fff0ae2f4e0
0x7fff0b7d4298: 0x00007ffff3e270ed 0x00007fff0b7d42f8
0x7fff0b7d42a8: 0x00007fff0ae2f4e0 0x00007fff0ae2f4e0
0x7fff0b7d42b8: 0x00007ffff3d99f42 0x00007fff0ae2f4e0
0x7fff0b7d42c8: 0x00007fff0b7d43f0 0x00007fff0b7d43f0
0x7fff0b7d42d8: 0x00007ffff3dc3a04 0x0000000000000001
0x7fff0b7d42e8: 0x0000000000000001 0x00007fff0b7d43f0
0x7fff0b7d42f8: 0x00007fff0ae2f4e0 0x00007fff0ae2f4e0
0x7fff0b7d4308: 0x0000000000000001 0x00007fff0b7d43f0
0x7fff0b7d4318: 0x00007ffff3d931cf 0x00007fff0ae2f4e0
0x7fff0b7d4328: 0x0000000000000001 0x00007fff0b7d43f0
and 100xg:
0x7fff0b7d4338: 0x00007ffff3dc42fe 0x0000000000000007
0x7fff0b7d4348: 0x0000000000000006 0x00007ffff637393e
0x7fff0b7d4358: 0x00007fff0ae2f480 0x00007fff0b7d4388
0x7fff0b7d4368: 0x00007fff0ae2f480 0x0000000000000060
0x7fff0b7d4378: 0x00007fff0ae01080 0x000000000000003e
0x7fff0b7d4388: 0x0000000000000001 0x00007fff0ae2f4e0
0x7fff0b7d4398: 0x000000000000003e 0x00007fff0ae01080
0x7fff0b7d43a8: 0x0000000013bd0d88 0x00007fffffffbfe0
0x7fff0b7d43b8: 0x00007fff0b7d4420 0x00007fffffffbfa8
0x7fff0b7d43c8: 0x00007fffffffbf50 0x00007fff0b7d4ab8
0x7fff0b7d43d8: 0x000000000000003b 0x0000000000000007
0x7fff0b7d43e8: 0x0000000000000006 0x00007fff0ae2f4e0
0x7fff0b7d43f8: 0x0000000000000004 0x0000000000000001
0x7fff0b7d4408: 0x00007fff0ae2f480 0x0000000000000004
0x7fff0b7d4418: 0x0000000000000001 0x00007fff0ae01080
0x7fff0b7d4428: 0x000000000000003e 0x000000000000003e
0x7fff0b7d4438: 0x00007fffffffbf50 0x00007fff0b7d4ab8
0x7fff0b7d4448: 0x000000000000003b 0x0000000000000007
0x7fff0b7d4458: 0x0000000000000006 0x00007fff0ae2f2a0
0x7fff0b7d4468: 0x000000000000003e 0x00007fff0ae01080
0x7fff0b7d4478: 0x000000000000003e 0x00007fff0ae2f4e0
0x7fff0b7d4488: 0x0000000000000001 0x00007fff0b7d45b0
0x7fff0b7d4498: 0x000001ff0ae2f2a0 0x00007fff0ae2f480
0x7fff0b7d44a8: 0x0000000000000000 0x00007ffff7bb2c60
0x7fff0b7d44b8: 0x00007ffff3daebc6 0x0000000000201000
0x7fff0b7d44c8: 0x0000000000000000 0x00007fffebd4f080
0x7fff0b7d44d8: 0x00007fff0ae2f2a0 0x000000000000003e
0x7fff0b7d44e8: 0x0100000000010301 0x00007fff0ae2f2a0
0x7fff0b7d44f8: 0x000000000000003e 0x000000000000003e
0x7fff0b7d4508: 0x00007fff0ae2f2a0 0x000000000000003e
0x7fff0b7d4518: 0x00007fff0ae2f2a0 0x000000000000003e
0x7fff0b7d4528: 0x00007fff0ae2f2a0 0x0000000060cc0baf
0x7fff0b7d4538: 0x00007fff0ae32180 0x00007fffffffbf50
0x7fff0b7d4548: 0x0000000000000000 0x00007fff0ae32240
0x7fff0b7d4558: 0x00007fff0ae32000 0x0000000000000006
0x7fff0b7d4568: 0x0000000000000007 0x000000000000003b
0x7fff0b7d4578: 0x00007fff0ae3e000 0x00007fff0b7d50b0
0x7fff0b7d4588: 0x00007fff0b7d50b0 0x00007fff0b7d4ab8
0x7fff0b7d4598: 0x00007fff0ae2f480 0x0000000000000004
0x7fff0b7d45a8: 0x0000000000000001 0x00007fff0ae32240
0x7fff0b7d45b8: 0x00007fff0ae32240 0x0000000000000000
0x7fff0b7d45c8: 0x00007fff0ae2f2a0 0x000000000000003e
0x7fff0b7d45d8: 0x0000000000000001 0x00007fff0ae2f480
0x7fff0b7d45e8: 0x0000000000000004 0x0000000000000001
0x7fff0b7d45f8: 0x0000000000000000 0x00007fff0ae3e000
0x7fff0b7d4608: 0x000000000000003b 0x0000000000000007
0x7fff0b7d4618: 0x0000000000000006 0x00007fffebda59d0
0x7fff0b7d4628: 0x00007ffff21bf5cd 0x00007fff0ae32180
0x7fff0b7d4638: 0x00007fff0ae32180 0x00007fff0ae32180
0x7fff0b7d4648: 0x0000000000000000 0x00007fff0b7d4858
The frame info of C++:
called by frame at 0x7fff0b7d44c0
source language c++.
Arglist at 0x7fff0b7d4330, args: server=0x7fff0ae2f498, region_buff=..., peer_id=62, snaps=..., index=62, term=140737324202302
Locals at 0x7fff0b7d4330, Previous frame's sp is 0x7fff0b7d4340
Saved registers:
rip at 0x7fff0b7d4338
expected first arg: 0x7fffffffbfe0
expected second arg: (0x7fff0ae01080, 0x3e)
expected third arg: 0x3b
expected 4th arg: (0x7fff0ae2f4e0, 1)
expected 5th, 6th arg: 7, 6
Newer GCC does one fewer push at the top of the function (saving one fewer call-preserved register). So it only needs to move RSP by an even multiple of 8 (0x120) to reach a 16-byte alignment boundary before the next call along with reserving enough space, not 0x128.
Both of these things change the distance between RBP and RSP, and thus the [RBP-0x130] offsets necessary to reach the space just above RSP to pass large structs by value on the stack.
I didn't check the math, but you can double-check yourself if you still think it's possible that GCC may have a correctness bug.
struct View only has two pointer-sized members. As such, the x86-64 System V ABI can pass it in a pair of registers.
And it has no constructors or destructors, or anything else, so the C++ ABI goes along with that. (I forget the exact rule used to decide whether an object must have an address and thus pass it on the stack. If your C++ vs. Rust code disagrees about that, it could lead to them disagreeing about how to pass it.)
Update since the problem did turn out to be a non-trivial struct:
GCC manual for the -fabi-version option:
Version 12, which first appeared in G++ 8, corrects the calling conventions for empty classes on the x86_64 target and for classes with only deleted copy/move constructors. It accidentally changes the calling convention for classes with a deleted copy constructor and a trivial move constructor.
Version 13, which first appeared in G++ 8.2, fixes the accidental change in version 12.
As such, the first 6 args (counting the structs as two args) will get passed in RDI, RSI, RDX, RCX, R8, R10 (in that order), with the rest on the stack. You can write a simple function to see where it looks for args:
int ffi_function(Opaque *server, View region_buf, uint64_t peer_id,
View snaps, uint64_t index, uint64_t term)
{
//return region_buf.len;
return region_buf.len - snaps.len + index;
}
GCC -Og -fverbose-asm makes the following asm, GCC11 on Godbolt:
ffi_function(Opaque*, View, unsigned long, View, unsigned long, unsigned long):
sub rdx, r9 # _3, tmp103
mov rax, rdx # _3, _3
add rax, QWORD PTR [rsp+8] # _3, index
ret
(You can see index is thus the first stack arg, right above the return address; the earlier ones are in registers. snaps.len is the last register arg, in R9.)
I guessed uint64_t as your ReturnType. If it's a struct larger than 2 registers, then you'll have a pointer to the return-value object as the first arg in RDI, and the other args bumped over by 1.
Your dump matches your expected args just fine. I reformatted it so I could add an annotation to each qword (8-byte) stack slot.
RSP:
0x7fff0b7d4338: 0x00007ffff3dc42fe (return addr)
0x0000000000000007 (expected 5th arg = 7)
0x0000000000000006 (expected 6th arg = 6)
0x00007ffff637393e (part of the caller's stack frame, not args)
...
The earlier args are all in registers.
If debug info isn't finding them perfectly, that's perhaps because you compiled with optimization; things get tricky and a callee might not keep a copy of its incoming args anywhere. Or even if not, getting debug info working right for this can be tricky.
sorry for my late reply. thanks again for #Peter Cordes.
The problem was finally located and addressed by one of my colleague.
it turns out that View was not defined purely trivial. A self defined ctor was added to the struct.
My colleague did twos things to fix the problem:
remove all ctors to keep the struct trivial
move all related ffi functions to extern C scope.
I haven't yet taken a look at the generated code but according to the report, the segfault has gone.
That is being said, I still think there may be something to think about carefully. If we follow the Itanium ABI strictly, one can see that View is still trivial for the purpose of call
and should be passed in the convention of C.
https://itanium-cxx-abi.github.io/cxx-abi/abi.html#non-trivial-parameters
So even though the original code was somewhat "messy", I think it can be used in FFI? (after all gcc-7 is doing good with that and -Wabi=11/12/13 didn't give any complaints to it).
Update:
checked the assembly just now. After the change, gcc-10 are generating exact the same code as gcc-7.
I have a vulnerability trigger (CVE-2018-18492) in Firefox, which crashes and gives SIGSEGV. I used breakpad minidump_stackwalk to get its stack trace from the minidump file produced with the crash. I got something like below:
Thread 0 (crashed)
0 0xd000b1000d
rax = 0x00005576254ffac0 rdx = 0x0000000000000000
rcx = 0x0000000000000001 rbx = 0x0000000000000000
rsi = 0x0000557623c5e040 rdi = 0x00005576239d7c60
rbp = 0x00007fffd0546890 rsp = 0x00007fffd0546568
r8 = 0x0000000044815f7a r9 = 0x00000000aa7e5e96
r10 = 0x0000000000000001 r11 = 0x0000000000000001
r12 = 0x0000557623c5e040 r13 = 0x00007fffd0546910
r14 = 0x00005576239d7c60 r15 = 0x0000557623c5e040
rip = 0x000000d000b1000d
Found by: given as instruction pointer in context
1 libxul.so!mozilla::dom::HTMLOptionsCollection_Binding::add [HTMLOptionsCollectionBinding.cpp : 165 + 0x1d]
rbp = 0x00007fffd0546a70 rsp = 0x00007fffd05468a0
rip = 0x00007f099629754c
Found by: previous frame's frame pointer
2 libxul.so!bool mozilla::dom::binding_detail::GenericMethod<mozilla::dom::binding_detail::NormalThisPolicy, mozilla::dom::binding_detail::ThrowExceptions>(JSContext*, unsigned int, JS::Value*) [BindingUtils.cpp : 3296 + 0x9]
rbx = 0x00007f099c307d10 rbp = 0x00007fffd0546b40
rsp = 0x00007fffd0546a80 r12 = 0x00000000000000fb
r13 = 0x00007fffd0546af0 r14 = 0x00007fffd0546ab0
r15 = 0x00007fffd0546ad0 rip = 0x00007f099638680d
Found by: call frame info
...
Then I ran with the same thing again with gdb (gdb /path/to/firefox/binary), it crashes again as expected, and I use bt to get the backtrace at the point of crash. However I got something slightly different:
#0 0x000055a17135b810 in ()
#1 0x00007f23dd134dea in nsINode::ReplaceOrInsertBefore(bool, nsINode*, nsINode*, mozilla::ErrorResult&) (this=0x55a1712a3550, aReplace=<optimized out>, aNewChild=<optimized out>, aRefChild=<optimized out>, aError=...) at /home/ug16zy2/firefox-63.0.3/dom/base/nsINode.cpp:2631
#2 0x00007f23dd8b0e7f in mozilla::dom::HTMLOptionsCollection_Binding::add(JSContext*, JS::Handle<JSObject*>, mozilla::dom::HTMLOptionsCollection*, JSJitMethodCallArgs const&) (cx=0x55a16e7867e0, obj=Python Exception <class 'gdb.error'> No type "Class" within class or namespace "js".:
0x7f23820aaf40, self=0x55a1701b1600, args=...) at /home/ug16zy2/firefox-63.0.3/objdir-ff-dbg/dom/bindings/HTMLOptionsCollectionBinding.cpp:165
#3 0x00007f23dd953158 in mozilla::dom::binding_detail::GenericMethod<mozilla::dom::binding_detail::NormalThisPolicy, mozilla::dom::binding_detail::ThrowExceptions>(JSContext*, unsigned int, JS::Value*) (cx=0x55a16e7867e0, argc=1, vp=0x55a16f509ed0) at /home/ug16zy2/firefox-63.0.3/dom/bindings/BindingUtils.cpp:3296
...
Note the call stack of two outputs. It seems that gdb gives one more function call ReplaceOrInsertBefore on top of add, whereas minidump did not.
Do you know what causes the difference between them and why?
Program received signal SIGSEGV, Segmentation fault.
0x400741e0 in std::_List_node_base::hook(std::_List_node_base*) ()
from /mnt/yaffs2/Cdatabox/lib/libstdc++.so.6
(gdb) bt
#0 0x400741e0 in std::_List_node_base::hook(std::_List_node_base*) ()
from /mnt/yaffs2/Cdatabox/lib/libstdc++.so.6
#1 0x00012df8 in std::list<std::list<Cbox::SteadyNode, std::allocator<Cbox::SteadyNode> >, std::allocator<std::list<Cbox::SteadyNode, std::allocator<Cbox::SteadyNode> > > >::_M_insert (this=0xbe9d1af0, __position=..., __x=...)
at /opt/arm-2008q3-linux/bin/../lib/gcc/arm-none-linux-gnueabi/4.3.2/../../../../arm-none-linux-gnueabi/include/c++/4.3.2/bits/stl_list.h:1342
#2 0x00012e30 in std::list<std::list<Cbox::SteadyNode, std::allocator<Cbox::SteadyNode> >, std::allocator<std::list<Cbox::SteadyNode, std::allocator<Cbox::SteadyNode> > > >::push_back (this=0xbe9d1af0, __x=...)
at /opt/arm-2008q3-linux/bin/../lib/gcc/arm-none-linux-gnueabi/4.3.2/../../../../arm-none-linux-gnueabi/include/c++/4.3.2/bits/stl_list.h:876
#3 0x0000d508 in Cbox::SteadyAnalysis::__dealSteady (this=0xbe9d1a98)
at ../include/class/SteadyAnalysis.h:237
#4 0x0000dc7c in Cbox::SteadyAnalysis::input (this=0xbe9d1a98,
weight=1467031, rawTime=1552067705)
at ../include/class/SteadyAnalysis.h:110
#5 0x0000deb4 in main (argc=2, argv=0xbe9d1d74) at SteadyAnalysis.cc:30
(gdb) disassemble
Dump of assembler code for function _ZNSt15_List_node_base4hookEPS_:
0x400741d0 <+0>: ldr r3, [r1, #4]
0x400741d4 <+4>: stm r0, {r1, r3}
0x400741d8 <+8>: ldr r2, [r1, #4]
0x400741dc <+12>: str r0, [r1, #4]
=> 0x400741e0 <+16>: str r0, [r2]
0x400741e4 <+20>: bx lr
End of assembler dump.
(gdb) i r
r0 0x31220 201248
r1 0xbe9d1af0 3197967088
r2 0x46 70
r3 0x46 70
r4 0xbe9d1a98 3197967000
r5 0x40dd4c00 1088244736
r6 0x4136629f 1094083231
r7 0x1e400000 507510784
r8 0x41d720ab 1104617643
r9 0x0 0
r10 0x31d20 204064
r11 0xbe9d1944 3197966660
r12 0x30a58 199256
sp 0xbe9d1928 0xbe9d1928
lr 0x12df8 77304
pc 0x400741e0 0x400741e0 <std::_List_node_base::hook(std::_List_node_base*)+16>
cpsr 0x60000010 1610612752
(gdb)
code:
//Type declaration
struct SteadyNode {
double mean;
int duration;
int startLine;
time_t startDetectedRawTime;
time_t endDetectedRawTime;
};
//Definition info
//list<SteadyNode>::iterator upIt, downIt;
//list<SteadyNode> steadyNodeList;
//list<list<SteadyNode> > sleepPiceList;
{
steadyNodeList.back().endDetectedRawTime = currentRawTime;
SteadyNode last = steadyNodeList.back();
if (onBedFlag == 1)
{
downIt = steadyNodeList.end();
list<SteadyNode> onBedMeanList;
onBedMeanList.splice(onBedMeanList.begin(), steadyNodeList, upIt, downIt);
steadyNodeList.clear();
steadyNodeList.push_back(last);
sleepPiceList.push_back(onBedMeanList); //<=== crash position
onBedMeanList.clear();
onBedFlag = -1;
}
else
{
steadyNodeList.clear();
steadyNodeList.push_back(last);
}
}
There is only one source code.
When I compiled successfully on the debian9 host, valgrind --leak-check=full tested no memory leaks and the program executed correctly.
On the arm platform, the compilation was successful, but the program got this error when it was executed. I hope to get everyone's help, thank you.
Solved.
Those gdb print info is not major problem.
For gcc4.4 and gcc4.3, STL list will not pre alloc element when you write code like list<DiyClass>, but in gcc6.3 will. That is why the same code can run well in gcc6.3 but segment fault in gcc4.3.
I guess some low level implements is different between diff gcc version. if someone know detail reason, give a answer, thanks.
My application crashes once in a while and generates coredump.
Each time stack is different. But each time when it dumps, i find that one of the pointer will be corrupt. and each time first 4 bytes of pointer value will be 0X100 (256). By this i can make out that this dump is because of memory corruption. But i have no idea as how to proceed. I ran all the static tools on the code. But i cant really attach valgrind (I have no access to site)
#0 0x00002b5775455738 in _STL::_List_base<int, _STL::allocator<int> >::clear (this=0x2b576debd408) at /home/enipcore/core/add-ons/include/stlport/stl/_list.c:72
72 in /home/xxxx/core/add-ons/include/stlport/stl/_list.c
(gdb) info locals
__tmp = 0x10084a957f0
__cur = 0x10084a957f0
(gdb)
I will share the info which i have, Pls suggest me how to proceed. I really do not know what info to give here. If any one want me to run any command and get memory print i can share.
(gdb) p &__tmp
$13 = (_STL::_List_node<int> **) 0x2b577b70e4b8
(gdb)
Registers, eax will have this wrong value
$13 = (_STL::_List_node<int> **) 0x2b577b70e4b8
(gdb) i r
rax 0x10084a957f0 1101737318384
rbx 0x2b5771e1fccb 47654572784843
rcx 0x2b576e1251d0 47654508843472
rdx 0x85 133
rsi 0x2b571729eee8 47653050773224
rdi 0x2b576debd408 47654506320904
rbp 0x2b577b70e4c0 0x2b577b70e4c0
rsp 0x2b577b70e4a0 0x2b577b70e4a0
r8 0x2b576d176480 47654492398720
r9 0x7bbe 31678
r10 0x2b5774f0a5e0 47654624077280
r11 0x2b57165c3f80 47653037293440
r12 0x2b577b716e68 47654733180520
r13 0x0 0
r14 0x2b57188221e0 47653073330656
r15 0x2b577b716e68 47654733180520
rip 0x2b5775455738 0x2b5775455738 <_STL::_List_base<int, _STL::allocator<int> >::clear()+40>
eflags 0x10206 [ PF IF RF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
fctrl 0x37f 895
fstat 0x0 0
ftag 0xffff 65535
fiseg 0x0 0
fioff 0x0 0
foseg 0x0 0
fooff 0x0 0
fop 0x0 0
mxcsr 0x1fa0 [ PE IM DM ZM OM UM PM ]
(gdb)
Some memory dump.
(gdb) x/20a 0x2b577b70e4b8 -20
0x2b577b70e4a4: 0x6debd40800002b57 0x84a957f000002b57
0x2b577b70e4b4: 0x84a957f000000100 0x7b70e4e000000100
0x2b577b70e4c4: 0x754557fb00002b57 0x7b70e55000002b57
0x2b577b70e4d4: 0x6debd40800002b57 0x7b70e50000002b57
0x2b577b70e4e4: 0x7545583100002b57 0x7b716e6800002b57
0x2b577b70e4f4: 0x6debd40800002b57 0x7b70e55000002b57
0x2b577b70e504: 0x75451de100002b57 0x6de161a000002b57
0x2b577b70e514: 0x100002b57 0x6debd40800000000
0x2b577b70e524: 0x6debd3c800002b57 0x2b57
0x2b577b70e534: 0x167f860800000000 0x71e1fccb00002b57
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))
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.