I have a binary that I have linked with the -bind_at_load argument to ld. On an ELF system, I'd use -Wl,-z,now and then readelf to verify that the DT_BIND_NOW flag was enabled on the binary. On OS X, how can I verify that the the appropriate flag in the mach header has been ste to honor -bind_at_load? What is the name of the flag, and what value should it be set to?
You can use otool -l /path/to/binary and inspect the LC_DYLD_INFO_ONLY load command. If the binary was linked with -bind_at_load, then the lazy bind offset/size are equal to 0: dyld won’t lazily bind symbols and all symbols are bound when the binary is loaded.
Sample output:
With -bind_at_load
Load command 4
cmd LC_DYLD_INFO_ONLY
cmdsize 48
rebase_off 8192
rebase_size 8
bind_off 8200
bind_size 224
weak_bind_off 0
weak_bind_size 0
lazy_bind_off 0
lazy_bind_size 0
export_off 8424
export_size 48
Without -bind_at_load
Load command 4
cmd LC_DYLD_INFO_ONLY
cmdsize 48
rebase_off 8192
rebase_size 8
bind_off 8200
bind_size 128
weak_bind_off 0
weak_bind_size 0
lazy_bind_off 8328
lazy_bind_size 104
export_off 8432
export_size 48
Related
I want to make sure my executable has debug info, trying the linux equivalent doesn't help:
$ file ./my_lovely_program
./my_lovely_program: Mach-O 64-bit executable arm64 # with debug info? without?
EDIT (from the answer of #haggbart)
It seems that my executable has no debug info (?)
$ dwarfdump --debug-info ./compi
./compi: file format Mach-O arm64
.debug_info contents: # <--- empty, right?
And with the other option, I'm not sure:
$ otool -hv ./compi
./compi:
Mach header
magic cputype cpusubtype caps filetype ncmds sizeofcmds flags
MH_MAGIC_64 ARM64 ALL 0x00 EXECUTE 19 1816 NOUNDEFS DYLDLINK TWOLEVEL WEAK_DEFINES BINDS_TO_WEAK PIE
This is very weird because I can perfectly debug it with lldb
(lldb) b main
Breakpoint 1: where = compi`main + 24 at main.cpp:50:9, address = 0x0000000100018650
(lldb) run
Process 6067 launched: '/Users/oren/Downloads/compi' (arm64)
Process 6067 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x0000000100018650 compi`main(argc=3, argv=0x000000016fdff7b8) at main.cpp:50:9
47 /*****************/
48 int main(int argc, char **argv)
49 {
-> 50 if (argc == 3)
51 {
52 const char *input = argv[1];
53 const char *output = argv[2];
Target 0: (compi) stopped.
Mach-O isn't like ELF: Its debug info is "sold separately" in a .dSYM file.
When you compile with -g you'll see a file gets generated along side your output, such that:
(~) gcc a.c -o /tmp/a -g2
(~) %ls -lFd /tmp/a /tmp/a.dSYM
-rwxr-xr-x 1 morpheus wheel 34078 Dec 6 12:56 /tmp/a*
drwxr-xr-x 3 morpheus wheel 96 Dec 6 12:56 /tmp/a.dSYM/
The .dSYM is a bundle (i.e. a directory structure) whose Contents/Resources/DWARF has the "companion file":
(~) %file /tmp/a.dSYM/Contents/Resources/DWARF/a
/tmp/a.dSYM/Contents/Resources/DWARF/a: Mach-O 64-bit dSYM companion file arm64
(~) %jtool2 -l /tmp/a.dSYM/Contents/Resources/DWARF/a | grep UUID
LC 00: LC_UUID UUID: BDD5C13E-F7B8-3B4D-BAF9-14DF3CD03724
(~) %jtool2 -l /tmp/a | grep UUID
LC 09: LC_UUID UUID: BDD5C13E-F7B8-3B4D-BAF9-14DF3CD03724
tools like lldb can figure out the debug data by trying for the companion file directory (usually in same location as the binary, or specified in a path), and then check the LC_UUID matches. This enables you to ship the binary without its dSym, and use the dSym when symbolicating a crash report (this is what Apple does). The debug info includes all local variable names, as well as debug_aranges (addr2line), etc:
(~) %jtool2 -l /tmp/a.dSYM/Contents/Resources/DWARF/a | grep DWARF
LC 07: LC_SEGMENT_64 Mem: 0x100009000-0x10000a000 __DWARF
Mem: 0x100009000-0x10000921f __DWARF.__debug_line
Mem: 0x10000921f-0x10000924f __DWARF.__debug_aranges
Mem: 0x10000924f-0x1000093dc __DWARF.__debug_info
Mem: 0x1000093dc-0x100009478 __DWARF.__debug_abbrev
Mem: 0x100009478-0x100009590 __DWARF.__debug_str
Mem: 0x100009590-0x1000095e8 __DWARF.__apple_names
Mem: 0x1000095e8-0x10000960c __DWARF.__apple_namespac
Mem: 0x10000960c-0x100009773 __DWARF.__apple_types
Mem: 0x100009773-0x100009797 __DWARF.__apple_objc
If you really want to get of any debug info - including, say, local function symbols (which are included by default in the binary), strip -d -x is your friend. This operates on the binary.
Note that running "dsymutil" (As suggested in other answers) can be a bit misleading, since in order to display information it will track down the accompanying dSym - which will be present on your machine, but not if you move the binary elsewhere.
If you run :
dsymutil -s ./my_lovely_propgram | grep N_OSO
and it shows output, it means there is debug info.
My plan to debug eBPF code using the latest gdb support. however the simulator crash after I run . so i tried regular "hello world" c code compiled with -g option (gcc -g hello_wold.c) got the same issue.
gdb version : GNU gdb (GDB) 12.0.50.20211105-git
Reading symbols from /root/hello_program/a.out...
(gdb) target sim
Connected to the simulator.
(gdb) sim memory-size 4Mb
(gdb) load
Loading section .interp, size 0x1c lma 238
Loading section .note.ABI-tag, size 0x20 lma 254
Loading section .note.gnu.build-id, size 0x24 lma 274
Loading section .gnu.hash, size 0x1c lma 298
Loading section .dynsym, size 0xc0 lma 2b8
Loading section .dynstr, size 0x96 lma 378
Loading section .gnu.version, size 0x10 lma 40e
Loading section .gnu.version_r, size 0x20 lma 420
Loading section .rela.dyn, size 0xd8 lma 440
Loading section .rela.plt, size 0x18 lma 518
Loading section .init, size 0x17 lma 530
Loading section .plt, size 0x20 lma 550
Loading section .plt.got, size 0x8 lma 570
Loading section .text, size 0x1c2 lma 580
Loading section .fini, size 0x9 lma 744
Loading section .rodata, size 0x10 lma 750
Loading section .eh_frame_hdr, size 0x3c lma 760
Loading section .eh_frame, size 0x10c lma 7a0
Loading section .init_array, size 0x8 lma 200dd8
Loading section .fini_array, size 0x8 lma 200de0
Loading section .jcr, size 0x8 lma 200de8
Loading section .dynamic, size 0x1e0 lma 200df0
Loading section .got, size 0x30 lma 200fd0
Loading section .got.plt, size 0x20 lma 201000
Loading section .data, size 0x10 lma 201020
Start address 580
Transfer rate: 17760 bits in <1 sec.
(gdb) run
Starting program: /root/hello_program/a.out
Fatal signal: Aborted
----- Backtrace -----
0x55ffda5588a7 gdb_internal_backtrace_1
/root/binutils-gdb/gdb/bt-utils.c:121
0x55ffda5588a7 _Z22gdb_internal_backtracev
/root/binutils-gdb/gdb/bt-utils.c:164
0x55ffda6506cd handle_fatal_signal
/root/binutils-gdb/gdb/event-top.c:896
0x7f89e035905f ???
/build/glibc-77giwP/glibc-2.24/signal/../sysdeps/unix/sysv/linux/x86_64/sigaction.c:0
0x7f89e0358fff __GI_raise
../sysdeps/unix/sysv/linux/raise.c:51
0x7f89e035a429 __GI_abort
/build/glibc-77giwP/glibc-2.24/stdlib/abort.c:89
0x55ffda8e2728 sim_engine_invalid_insn
/root/binutils-gdb/sim/bpf/traps.c:37
0x55ffda8da83d execute
/root/binutils-gdb/sim/bpf/mloop-le.c:119
for bpf code i compiled with ( clang -target bpf -g -O2 -c hello_world.c)
hello world bpf code
#include <linux/bpf.h>
#include "bpf_helpers.h"
#include <stdlib.h>
#include <stdio.h>
int bpf_prog(void *ctx) {
char buf[] = "Hello World!\n";
bpf_trace_printk(buf, sizeof(buf));
bpf_trace_printk(buf, sizeof(buf));
bpf_trace_printk(buf, sizeof(buf));
bpf_trace_printk(buf, sizeof(buf));
return 0;
}
Error
Type "apropos word" to search for commands related to "word"...
Reading symbols from /root/ebpf_test/hello_world.o...
(No debugging symbols found in /root/ebpf_test/hello_world.o)
(gdb) target sim
Connected to the simulator.
(gdb) sim memory-size 4Mb
(gdb) load
Loading section .text, size 0x28 lma 0
Loading section .rodata.str1.1, size 0xd lma 0
Start address 0
Transfer rate: 424 bits in <1 sec.
(gdb) run
Starting program: /root/ebpf_test/hello_world.o
Fatal signal: Aborted
----- Backtrace -----
0x562154f4a63b gdb_internal_backtrace_1
/root/binutils-gdb/gdb/bt-utils.c:121
0x562154f4a63b _Z22gdb_internal_backtracev
/root/binutils-gdb/gdb/bt-utils.c:164
0x56215504d2ba handle_fatal_signal
/root/binutils-gdb/gdb/event-top.c:896
0x7f6f1a85503f ???
0x7f6f1a854fb7 ???
0x7f6f1a856920 ???
0x5621552fb9f8 sim_engine_invalid_insn
/root/binutils-gdb/sim/bpf/traps.c:37
0x5621552f3aaa execute
/root/binutils-gdb/sim/bpf/mloop-le.c:119
0x5621552f3aaa bpfbf_ebpfle_engine_run_full
/root/binutils-gdb/sim/bpf/mloop-le.c:222
0x5621552ddee4 engine_run_1
same happen when i try to debug an ebpf program. any help is appreciated to understand where to begin fixing the issue.
thanks
Looking at the number of sections that GDB loaded, I don't believe your object file was compiled as eBPF bytecode. Was gcc -g hello_world.c the command you used to produce it? This will compile instructions from your host architecture, likely x86_64.
Compiling to eBPF
To compile to eBPF instructions instead, most people use clang rather than gcc. There is a gcc backend for eBPF now, but I don't know what its status is, I'm just aware that its eBPF support is not quite as complete as clang/LLVM. With clang, you can run:
$ clang -target bpf -g -O2 -c hello_world.c
This will produce an object file containing eBPF instructions. Note that I'd usually add -Wall and -o hello_world.o to display potential warnings and to name the output file. The resulting object file should work in gdb, provided you have a recent version with support for eBPF (see here otherwise).
If you care about a specific eBPF instruction set (if you're getting started, you probably don't), you can specify it by compiling in two steps instead, and passing the relevant option via the -mcpu option for llc:
$ clang -g -O2 -Wall -emit-llvm -c hello_world.c -o - | \
llc -march=bpf -mcpu=probe -filetype=obj -o hello_world.o
See also this resource for details and compiling into and from assembly.
I compiled a simple program such as
int main()
{
return 0;
}
using Clang into an executable and asked otool to report the load commands generated by the compiler. The one I'm interested in is LC_SEGMENT_64, in particular the one that describes the __TEXT segment within the file. The description I get is this:
$ otool -lV foo
foo:
Load command 0
cmd LC_SEGMENT_64
cmdsize 72
segname __PAGEZERO
vmaddr 0x0000000000000000
vmsize 0x0000000100000000
fileoff 0
filesize 0
maxprot ---
initprot ---
nsects 0
flags (none)
Load command 1
cmd LC_SEGMENT_64
cmdsize 312
segname __TEXT
vmaddr 0x0000000100000000
vmsize 0x0000000000001000
fileoff 0
filesize 4096
maxprot rwx
initprot r-x
nsects 3
flags (none)
Section
sectname __text
segname __TEXT
addr 0x0000000100000f90
size 0x000000000000000f
offset 3984
align 2^4 (16)
reloff 0
nreloc 0
type S_REGULAR
attributes PURE_INSTRUCTIONS SOME_INSTRUCTIONS
reserved1 0
reserved2 0
My question is: why is the fileoff field in the second load command set to zero?
Apple's documentation for this field states that
The file is mapped starting at fileoff to the beginning of the segment in memory, vmaddr.
This, initially, led me to believe that this field, in conjunction with filesize, indicated the loader something like this: "Take the contents of the file from fileoff to fileoff + filesize and this is the sequence of instructions you're gonna ask the processor to run". But my assumption doesn't hold if this value is zero, of course.
I thought that, since the segment has at least one section, the loader will use the value of the respective offset in the section's description to locate the code to run, and hence such value isn't exactly needed --- we can see that, in fact, the first section within this segment has a value for the offset field (in this case 3984, which I validated with otool -s __TEXT __text -j foo and indeed refers to the offset at which this section is located within the file).
But, if I do the same thing to the object file generated from the same source file (i.e. a file with type MH_OBJECT instead of MH_EXECUTE), the result I get is this:
$ otool -lV foo.o
foo.o:
Load command 0
cmd LC_SEGMENT_64
cmdsize 312
segname
vmaddr 0x0000000000000000
vmsize 0x0000000000000070
fileoff 464
filesize 112
maxprot rwx
initprot rwx
nsects 3
flags (none)
Section
sectname __text
segname __TEXT
addr 0x0000000000000000
size 0x000000000000000f
offset 464
align 2^4 (16)
reloff 0
nreloc 0
type S_REGULAR
attributes PURE_INSTRUCTIONS SOME_INSTRUCTIONS
reserved1 0
reserved2 0
In this case, the load command does have a value for its fileoff field, which is the same as the one for its first section, __text.
otool makes it hard to realize, but the answer is simple - Observe here:
$ jtool -v -l /tmp/a | grep SEG
LC 00: LC_SEGMENT_64 Mem: 0x000000000-0x100000000 File: Not Mapped ---/--__PAGEZERO
LC 01: LC_SEGMENT_64 Mem: 0x100000000-0x100001000 File: 0x0-0x1000 r-x/rw__TEXT
LC 02: LC_SEGMENT_64 Mem: 0x100001000-0x100002000 File: 0x1000-0x1098 r--/rw__LINKEDIT
The __TEXT segment is mapped from the beginning of the file (or slice, if fat ("universal")). That is, with the Mach-O header. This is actually a feature, because the Mach-O then gets parsed by dyld (your friendly loader) for other load commands (notably libraries). The other issue is that __TEXT.__text is often in the very same page , so you'd have to map the whole page anyway.
I'm looking at the disassembly of some x86_64 code with Apple's otool. Here's a sample of the disassembly, as outputted by otool:
0000000100055de4 movq $0x00000000,%rax
Only the last 4 bytes in that offset, the 00055de4, represent the file address of that instruction. I can open a hex editor and navigate to 0x55de4 and the movq instruction is there.
However, I noticed that gdb only works when I include all 8 bytes in the address, including the mysterious 1. break *0x0000000100055de4 works as expected, while break *0x00055de4 never triggers.
Every 64-bit binary I have analyzed with otool shows this pattern. It obviously doesn't apply to 32-bit addresses.
So, if 0x55de4 is the actual address, why do otool and gdb use 0x0000000100055de4?
__PAGEZERO, the first load command in a 64 bit Mach-O binary, specifies a segment size of 0x100000000 in virtual memory.
$ otool -lV binary
command 0
cmd LC_SEGMENT_64
cmdsize 72
segname __PAGEZERO
vmaddr 0x0000000000000000
vmsize 0x0000000100000000
When you do break *0x00055de4 your breakpoint ends up in this segment of zeros, which explains why it's never hit. 0x0000000100055de4 is the address of the instruction (found at 0x55de4 in the binary) when loaded into virtual memory.
For 32 bit binaries the __PAGEZERO size is 0x1000, which explains why the pattern does not apply.
I'm using valgrind to debug a binary which uses loadable libraries via dlopen.
On debian stable the stacktrace does not contain symbols for calls inside the loadable lib.
| | ->11.55% (114,688B) 0x769492C: ???
| | | ->11.55% (114,688B) 0x7697289: ???
| | | ->11.55% (114,688B) 0x769806F: ???
| | | ->11.55% (114,688B) 0x419812: myfunc (main.c:1010)
Valgrind on debian unstable works fine and the symbols are properly resolved. So I started looking what is different.
I have these packages on both systems (valgrind was updated to 3.7 from unstable):
ii valgrind 1:3.7.0-1+b1
ii libtool 2.2.6b-2
ii gcc 4:4.4.5-1
ii binutils 2.20.1-16
The libs are not stripped and contain debuginfo:
ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=0x33ffd210859178c15bb3923c5491e1a1b6065015, not stripped
Looking closer I noticed that the size of the libraries are different, on debian unstable the lib is slightly bigger. Comparing them with readelf, the size of the debug info is bigger.
[26] .debug_aranges PROGBITS 0000000000000000 00a74c 000090 00 0 0 1
[27] .debug_pubnames PROGBITS 0000000000000000 00a7dc 000385 00 0 0 1
[28] .debug_info PROGBITS 0000000000000000 00ab61 00512f 00 0 0 1
[29] .debug_abbrev PROGBITS 0000000000000000 00fc90 0006e2 00 0 0 1
[30] .debug_line PROGBITS 0000000000000000 010372 002314 00 0 0 1
[31] .debug_str PROGBITS 0000000000000000 012686 0019d3 01 MS 0 0 1
[32] .debug_loc PROGBITS 0000000000000000 014059 000f24 00 0 0 1
[33] .debug_macinfo PROGBITS 0000000000000000 014f7d 179082 00 0 0 1
[34] .debug_ranges PROGBITS 0000000000000000 18dfff 000060 00 0 0 1
This makes me think that something is missing from the debug info section from the binaries built on debian stable. Now my question is: why and how are the binaries different? The tools (gcc, libtool, binutils) used in the build are the same, including the compiler/linker flags and commands (I checked with diff on make's output).
Update:
The debug_info section size difference came from the fact that the full path of the source file is stored there as well and the build home was different. Also there are different openssl versions on unstable/stable which added some different symbols to the debug_info section. Hence the difference in debug_info size.
Running valgrind in debug mode (-d -v -v) shows that it reads symbols from the loadable lib in both cases:
--19837-- Reading syms from /usr/lib/myplugin.so (0x6c62000)
If you are using dlopen for the loadable library, chances are that it was unloaded before the program terminates. Therefore Valgrind is unable to resolve its symbols. Try to avoid calling dlclose on this library. See http://valgrind.org/docs/manual/faq.html#faq.unhelpful for more information.