2

Having the following assembly source:

# hello_asm.s
# as hello_asm.s -o hello_asm.o
# ld hello_asm.o -e _main -o hello_asm
.section __DATA,__data
str:
  .asciz "Hello world!\n"

.section __TEXT,__text
.globl _main
_main:
  movl $0x2000004, %eax           # preparing system call 4
  movl $1, %edi                    # STDOUT file descriptor is 1
  movq str@GOTPCREL(%rip), %rsi   # The value to print
  movq $100, %rdx                 # the size of the value to print
  syscall

#
# EXITING
#
  movl $0, %ebx
  movl $0x2000001, %eax           # exit 0
  syscall

by compiling and linking with the following instructions:

as sum.s -g -o sum.o
ld -arch x86_64 -e main -L /Library/Developer/CommandLineTools/SDKs/MacOSX10.15.sdk/usr/lib -lSystem sum.o -o sum

and by trying to debug it on LLDB, I get the following result:

❯❯❯❯ lldb sum.o                                                                                                                   ~/D/test
(lldb) target create "sum.o"
Current executable set to '/Users/mbertamini/Downloads/test/sum.o' (x86_64).
(lldb) list
(lldb) b 16
error: No selected frame to use to find the default file.
error: No file supplied and no default file available.
(lldb)

This is the dwarf:

❯❯❯❯ dwarfdump sum.o                                                                                                                     ~/D/t/summ
sum.o:  file format Mach-O 64-bit x86-64

.debug_info contents:
0x00000000: Compile Unit: length = 0x00000094 version = 0x0004 abbr_offset = 0x0000 addr_size = 0x08 (next unit at 0x00000098)

0x0000000b: DW_TAG_compile_unit
              DW_AT_stmt_list   (0x00000000)
              DW_AT_low_pc  (0x0000000000000000)
              DW_AT_high_pc (0x0000000000000026)
              DW_AT_name    ("sum.s")
              DW_AT_comp_dir    ("<filepath>")
              DW_AT_producer    ("Apple clang version 12.0.0 (clang-1200.0.32.27)")
              DW_AT_language    (DW_LANG_Mips_Assembler)

0x0000007e:   DW_TAG_label
                DW_AT_name  ("main")
                DW_AT_decl_file ("<filepath-file>")
                DW_AT_decl_line (10)
                DW_AT_low_pc    (0x0000000000000000)
                DW_AT_prototyped    (0x00)

0x00000095:     DW_TAG_unspecified_parameters

0x00000096:     NULL

0x00000097:   NULL
❯❯❯❯ as -v                                                                                                                               ~/D/t/summ
Apple clang version 12.0.0 (clang-1200.0.32.27)
Target: x86_64-apple-darwin20.2.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
 "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang" -cc1as -triple x86_64-apple-macosx11.0.0 -filetype obj -main-file-name - -target-cpu penryn -fdebug-compilation-dir /Users/mbertamini/Downloads/test/summ -dwarf-debug-producer "Apple clang version 12.0.0 (clang-1200.0.32.27)" -dwarf-version=4 -mrelocation-model pic -o a.out -

what's the problem? How am I supposed to do?

Bertuz
  • 2,058
  • 2
  • 21
  • 37
  • FYI, `movq str@GOTPCREL(%rip), %rsi` is pointlessly inefficient when you could simply `lea str(%rip), %rsi`. I've seen other SO questions where people did this for x86-64 MacOS; is there some bad example floating around that people are copying? – Peter Cordes Jan 17 '21 at 21:25
  • it could be this one: http://www.idryman.org/blog/2014/12/02/writing-64-bit-assembly-on-mac-os-x/ – Bertuz Jan 17 '21 at 22:38
  • 1
    Ugh, that claims it can *only* be accessed via the GOT. Static data can be accessed directly with RIP-relative addressing modes unless you want to support symbol interposition in shared libraries. (e.g. override a definition with LD_PRELOAD). The distance between data and text is a link-time constant so you can access data as easily as you can `call rel32` another function, if you use RIP-relative LEA for the address or just plain RIP-relative addressing with other instructions for the data. Just like you can access the GOT with RIP-relative addressing. – Peter Cordes Jan 18 '21 at 00:32
  • @PeterCordes: Why not post a gist of what you are preaching; I'm not disagreeing with you, although it would be nice to see what the difference actually is. – l'L'l Jan 18 '21 at 00:34
  • 1
    I already posted the one instruction that changes, `lea str(%rip), %rsi`. I've written multiple SO answers about it, including [How to load address of function or label into register](https://stackoverflow.com/q/57212012) / [Why are global variables in x86-64 accessed relative to the instruction pointer?](https://stackoverflow.com/q/56262889), and so have other people: [Why does this MOVSS instruction use RIP-relative addressing?](https://stackoverflow.com/q/44967075) – Peter Cordes Jan 18 '21 at 00:38
  • @PeterCordes actually I miss the guts of the rip addressing (somebody is at his very fist asm day here). Could you comment a link explaining it? – Bertuz Jan 18 '21 at 01:21
  • 1
    @Bertuz: [How do RIP-relative variable references like work?](https://stackoverflow.com/q/54745872) comes close (and does mention AT&T syntax). Maybe also [Referencing the contents of a memory location. (x86 addressing modes)](https://stackoverflow.com/q/34058101) about what the machine can do. And/or read Intel or AMD's manuals or keep going with tutorials. Also https://wiki.osdev.org/X86-64_Instruction_Encoding#RIP.2FEIP-relative_addressing shows how it works in machine code. Also related: [Why use RIP-relative addressing in NASM?](https://stackoverflow.com/q/31234395) – Peter Cordes Jan 18 '21 at 01:28

1 Answers1

3

The issue is that the source file for which the debugging info is mapped should be used (sum.s):

$ as sum.s -g -o sum.o
$ ld -arch x86_64 -e _main -macosx_version_min 10.13 -lSystem sum.o -o sum
$ lldb sum
(lldb) target create "sum"
Current executable set to 'sum' (x86_64).
(lldb) b sum.s:16
Breakpoint 1: where = sum`main + 26, address = 0x0000000100000fac
(lldb) 

When assembling use the -O0 optimization along the -g Code Generation Option.
(This is important only when compiling with clang; this doesn't apply with as)

lldb: resolving breakpoints to locations

l'L'l
  • 40,316
  • 6
  • 77
  • 124
  • Does `-O0` even do anything for `as`, the assembler? For GNU Binutils, I think `-O0` is already the default, e.g. it doesn't optimize `mov $1, %rax` to `mov $1, %eax`. Even if your assembler optimizes single instructions, that shouldn't stop it from making useful debug info; the number and order of instructions won't change, just sometimes their size. (Very much unlike `clang -O0` vs. `clang -O2` for a `.c` source file). I guess bugs or design limitations are possible, but it's a surprising claim. – Peter Cordes Jan 18 '21 at 00:36
  • 1
    @PeterCordes: Setting the optimization level in addition to the debug option makes all the difference in whether the compiler can associate the debug map object properly or not. In the OP's command above it really comes down to that alone for generating the symbols; it's common with `llvm` in general. – l'L'l Jan 18 '21 at 00:49
  • But why? Even an optimizing assembler (*not compiler*) doesn't change the relationship between source lines and instructions, only the possible length in bytes of the instruction. This is a `.s`, not a `.c` file. I suspect the only important part of that `as` command is `-g` to make source-line debug info at all. (Otherwise you can just use disassembly mode and set breakpoints by address.) – Peter Cordes Jan 18 '21 at 00:57
  • I think you are correct for `as` the optimization makes no difference. In the case of normal compiling with clang the optimization level used with `-g` does make a difference obviously (`-O0` is what is recommended). I had mistakenly used `clang` on said code before posting this answer and the old `dSYM` was being used which couldn't be mapped. I think the main issue is the correct file was not used to set the breakpoint upon, so there you have it (answer edited). – l'L'l Jan 18 '21 at 01:16
  • Yes, like I said in my first comment, the situation for `as` is very much unlike `clang -O2 foo.c`. (But note that `-O0` is the default for clang anyway.) In my experience on GNU/Linux, `clang -O2 -g foo.c` does make source-line debugging still possible, but it jumps around as you step by instruction because the compiler will optimize across / between C statements, hence it is useful when actually compiling C: [Why does clang produce inefficient asm with -O0 (for this simple floating point sum)?](https://stackoverflow.com/q/53366394) – Peter Cordes Jan 18 '21 at 01:21
  • 1
    @PeterCordes: If you check the comments for the answer I previously posted in regards to `dSYM` someone said that they had to actually set `-O0` for `-g` to even work, so I'm not sure what's going on there (maybe they had multiple optimizations set?). But in the llvm docs they do mention "debug information works best at -O0. When more than one option starting with -g is specified, the last one wins"... so whether they mean set it regardless of whether it's default or not is unclear. – l'L'l Jan 18 '21 at 01:28
  • 1
    Obviously the LLVM docs that mention `-O0` are talking about code-gen from C or LLVM-IR, not just using the built-in assembler. Like I said, debugging is *possible* in general (on most platforms) with `-O2 -g`, but not nearly as good especially for small-scale single-stepping. Assembling a `.s` is *fundamentally* different from compiling a `.c` because with a `.s` you've already specified the instructions; LLVM isn't doing code-gen at all, only translating your asm source 1:1 into machine code. – Peter Cordes Jan 18 '21 at 01:36
  • 1
    Of course it's completely different from your `clang -O0 -g foo.c` answer when clang has to effectively *write* a `.s` and then assemble it. (Unlike GCC, clang doesn't *actually* make a text .s from a .c, it just uses internal data structures before doing code-gen. GCC truly does, though.) Anyway, TL:DR: this is why we make a big deal out of saying "assemble" asm, not "compile". – Peter Cordes Jan 18 '21 at 01:38