4

Explanation: I am using GDB to disassembly my self-written ELF64 executables which are assembled using NASM. When I try to disassemble using disassemble main, I only get the following output:

Dump of assembler code for function main:
0x0000000000401110 <+0>:     mov    rbp,rsp
0x0000000000401113 <+3>:     mov    ebx,0x400
End of assembler dump.

GDB obviously only gives me the first two lines, the ones before the loop whose start is indicated with .loop_clear begins. When I try to specify lines like disassemble 0x0000000000401116, 0x0000000000401119, I get the following output:

0x0000000000401116 <main+6>: add    BYTE PTR [rax],al
0x0000000000401118 <main.loop_clear+0>:      cmp    rbx,0x0

As can be seen, there are lines that aren't shown in the first dump. In the next try, I try to explicitely disassemble main.loop_clear by calling disassemble main.loop_clear but am left with Attempt to extract a component of a value that is not a structure.

Obviously the commands I am using or the syntax is wrong, but I couldn't find useful information during my research in order to fix this issue.

Question: Hence my question: How is it possible to create a complete and coherent dump of the disassembled executable if the function that is being disassembled contains labels/loops? Furthermore, is it possible to disassemble the loop for itself?

Source:

global main

section .text
main:

    ;initialize base pointer (rbp)
    mov     rbp, rsp

    ;set stack frame size and clear
    mov     rbx, 0x400
    .loop_clear:
    cmp     rbx, 0x0
    je      .exit_clear
    mov     byte [rsp], 0x0
    dec     rsp
    dec     rbx
    jmp     .loop_clear
    .exit_clear:

    ;prepare array index pointer
    mov     rcx, rsp

    ;transpiled brainfuck source
    mov bl, [rcx]
    add bl, 65
    mov [rcx], bl
    mov rsi, rcx
    mov rdx, 0x1
    mov rdi, 0x1
    mov rax, 0x1
    syscall               ; write(fd=1, buf, size=1 byte)

    ;exit gracefully
    mov     rax, 0x3
    mov     rdi, 0x0
    syscall               ; close(0)
    ret
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
llambdaa
  • 65
  • 6
  • If you specified `global main.loop_clear` for each of the local labels perhaps that would work around part of the issue. Just guessing though. – ecm Jul 02 '20 at 10:26
  • Oh gdb might not understand that a dot can be part of a label though. – ecm Jul 02 '20 at 10:27
  • 1
    https://stackoverflow.com/questions/38944547/break-at-local-label-using-gdb-for-nasm-assembly#comment65248615_38944547 Quoting the label with a dot may help. – ecm Jul 02 '20 at 10:28
  • Another related question: https://stackoverflow.com/questions/33624619/how-to-not-emit-local-symbols-in-nasm-so-that-gdb-disas-wont-stop-at-them – ecm Jul 02 '20 at 10:34
  • Thank you very much, this is working! I have to admit, that I probably could have come up with the same solution if I would have spent more time searching ;) Sorry for not putting in enough effort – llambdaa Jul 02 '20 at 11:05

2 Answers2

3

NASM constructs label names by concatenating local labels onto the last non-local label. These show up in the symbol table as regular labels, which of course GDB expects to only find on whole functions.

You can do disas 'main.loop_clear' - quoting the label name prevents the . from being treated as the C struct-member operator.

I usually use GDB's layout reg to show the disassembly window which avoids this problem for the label names NASM constructs. When stopped at the current position, disassembly continues past labels. And you can scroll forward/backward in disassembly using the arrow keys. Plus you get colour highlighting of the register(s) that changed when you stepped. See the bottom of https://stackoverflow.com/tags/x86/info for more GDB tips, and also the GDB manual for Text UI mode

My ~/.gdbinit contains:

set disassembly-flavor intel
layout reg

set print static-members off
set print pretty on

Running gdb ./a.out and then starti to stop before any user-space instruction, or in this case start (to stop at the top of main) gives me output like this:

...

│r14            0x0                 0                                       r15            0x0                 0                                       │
│rip            0x401110            0x401110 <main>                         eflags         0x246               [ PF ZF IF ]                            │
│cs             0x33                51                                      ss             0x2b                43                                      │
│ds             0x0                 0                                       es             0x0                 0                                       │
│fs             0x0                 0                                       gs             0x0                 0                                       │
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
│B+>0x401110 <main>                 mov    rbp,rsp                                                                                                     │
│   0x401113 <main+3>               mov    rbx,0x400                                                                                                   │
│   0x40111a <main.loop_clear>      cmp    rbx,0x0                                                                                                     │
│   0x40111e <main.loop_clear+4>    je     0x40112c <main.exit_clear>                                                                                  │
│   0x401120 <main.loop_clear+6>    mov    BYTE PTR [rsp],0x0                                                                                          │
│   0x401124 <main.loop_clear+10>   dec    rsp                                                                                                         │
│   0x401127 <main.loop_clear+13>   dec    rbx                                                                                                         │
│   0x40112a <main.loop_clear+16>   jmp    0x40111a <main.loop_clear>                                                                                  │
│   0x40112c <main.exit_clear>      mov    rcx,rsp                                                                                                     │
│   0x40112f <main.exit_clear+3>    mov    bl,BYTE PTR [rcx]                                                                                           │
│   0x401131 <main.exit_clear+5>    add    bl,0x41                                                                                                     │
│   0x401134 <main.exit_clear+8>    mov    BYTE PTR [rcx],bl                                                                                           │
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
native process 46953 In: main                                                                                                        L7    PC: 0x401110 
--Type <RET> for more, q to quit, c to continue without paging--
Reading symbols from ./a.out...
(gdb) start
Temporary breakpoint 1 at 0x401110: file bf.asm, line 7.
Starting program: /tmp/a.out

Temporary breakpoint 1, main () at bf.asm:7
(gdb) 

Note that layout reg has gotten really flaky in recent GDB for executables built from asm source. e.g. tui disab at this point would crash GDB. And it's not rare for GDB to crash when the program exits. I guess I should report some bugs if this isn't common knowledge.

But it's usable enough still.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
2

You could fix this by removing the local labels (with some loss of debuggability):

$ nasm -f elf64 foo.asm && nm foo.o
0000000000000000 T main
000000000000001a t main.exit_clear
0000000000000008 t main.loop_clear

$ strip --strip-unneeded foo.o && nm foo.o
0000000000000000 T main
gdb -q --batch -ex 'disas main' foo.o

Dump of assembler code for function main:
   0x0000000000000000 <+0>:     mov    %rsp,%rbp
   0x0000000000000003 <+3>:     mov    $0x400,%ebx
   0x0000000000000008 <+8>:     cmp    $0x0,%rbx
   0x000000000000000c <+12>:    je     0x1a <main+26>
   0x000000000000000e <+14>:    movb   $0x0,(%rsp)
   0x0000000000000012 <+18>:    dec    %rsp
   0x0000000000000015 <+21>:    dec    %rbx
   0x0000000000000018 <+24>:    jmp    0x8 <main+8>
   0x000000000000001a <+26>:    mov    %rsp,%rcx
   0x000000000000001d <+29>:    mov    (%rcx),%bl
   0x000000000000001f <+31>:    add    $0x41,%bl
   0x0000000000000022 <+34>:    mov    %bl,(%rcx)
   0x0000000000000024 <+36>:    mov    %rcx,%rsi
   0x0000000000000027 <+39>:    mov    $0x1,%edx
   0x000000000000002c <+44>:    mov    $0x1,%edi
   0x0000000000000031 <+49>:    mov    $0x1,%eax
   0x0000000000000036 <+54>:    syscall 
   0x0000000000000038 <+56>:    mov    $0x3,%eax
   0x000000000000003d <+61>:    mov    $0x0,%edi
   0x0000000000000042 <+66>:    syscall 
   0x0000000000000044 <+68>:    retq   
End of assembler dump.

Or you could single-quote the main.loop_clear in GDB disas command.

Employed Russian
  • 199,314
  • 34
  • 295
  • 362