汇编运行与调试 on macOS

    科技2022-09-02  109

    汇编运行与调试 on macOS

    工具:Sublime Text 3;GDB;Nasm;环境:macOS 10.15.4处理器:Intel Core i5

    0.工具准备

    上面👆三个工具下载好配置好就行,只说一点我踩的坑:gdb调试需要关闭SIP。

    1.编写程序代码

    SECTION .data ; 在显示器上输出hello, world ; _main的前段系统调用了write,后半段系统调用了exit ; write(int fd, const void *buffer, size_t nbytes) ; exit(int status) msg: db "HelloWorld!", 0x0a ;0x0a就是C语言的'\0'字符串结束符 len: equ $-msg ;变量len等于msg的长度 SECTION .text global _main _main: mov rax,0x2000004 ;0x2000004 表示 syscall 调用号 write mov rdi,1 ;第一个参数:文件描述符(fd),1代表标准输出stdout,也就是fd的值 mov rsi,msg ;第二个参数:要输出的字节序列(buffer),syscall 调用会到 rsi 来获取字符 mov rdx,len ;第三个参数:字节序列的长度 syscall mov rax,0x2000001 ;0x2000001 表示退出 syscall mov rdi,0 syscall ; 通过 syscall 指令进行系统调用时,约定的传递参数的寄存器依次为 rdi 、 rsi 、 rdx 、 rcx 。 ; 根据位数不同,寄存器也有不一样的名字。16位寄存器有ax,bx,cx,dx,bp,si,sp,ip等,32位在这些前面加了前缀e即eax等等,64位用的不是e而是r即rax等等。

    保存为test.asm。

    2.编译、链接、运行

    在终端将工作目录移动到test.asm下,进行编译:

    nasm -f macho64 -o test.o -g test.asm

    编译完成以后会生成test.o链接文件,下面进行链接:

    ld -o test -e _main test.o -macosx_version_min 10.15 -static

    链接完成以后会生成test二进制可执行文件。

    直接执行test文件,可以看到结果:

    f@fdeMBP test % ./test HelloWorld! f@fdeMBP test %

    3.调试

    调试需要工具GDB。

    依然将工作目录移动到test.asm下,执行以下命令进入调试环节:

    gdb test

    顺利的话,可以看到(gdb)的标识符。

    先在main函数处设置一个断点:

    (gdb) b main

    可以得到输出

    Breakpoint 1 at 0x100000fd9

    说明main函数的地址就在0x100000fd9。

    接下来运行程序:

    (gdb) r

    程序会在我们设置的端点处停下,并得到如下输出:

    Starting program: /path/to/test

    [New Thread 0x2703 of process 8812]

    [New Thread 0x1b03 of process 8812]

    Thread 2 hit Breakpoint 1, 0x0000000100000fd9 in main ()

    0x0000000100000fd9 in main ()说明运行到main函数在内存中的0x0000000100000fd9处指令停止,此指令并未运行。

    此时可以查看寄存器的值:

    (gdb) info registers

    得到如下输出:

    rax 0x0 0

    rbx 0x0 0

    rcx 0x0 0

    rdx 0x0 0

    rsi 0x0 0

    rdi 0x0 0

    rbp 0x0 0x0

    rsp 0x7ffeefbffa10 0x7ffeefbffa10

    r8 0x0 0

    r9 0x0 0

    r10 0x0 0

    r11 0x0 0

    r12 0x0 0

    r13 0x0 0

    r14 0x0 0

    r15 0x0 0

    rip 0x100000fd9 0x100000fd9

    eflags 0x202 [ IF ]

    cs 0x2b 43

    ss

    ds

    es

    fs 0x0 0

    gs 0x0 0

    fs_base

    gs_base

    寄存器名字和16位寄存器稍有不同,这个在最上面的程序注释中已经说明,主要是因为64位寄存器在前面加了个r。

    得到的输出中,第一个是寄存器名字,第二个是寄存器的16进制值,第三个是其10进制值。

    接下来运行一条指令:

    (gdb) nexti

    0x0000000100000fde in main ()

    再查看寄存器的值:

    rax 0x2000004 33554436

    rbx 0x0 0

    rcx 0x0 0

    rdx 0x0 0

    rsi 0x0 0

    rdi 0x0 0

    rbp 0x0 0x0

    rsp 0x7ffeefbffa10 0x7ffeefbffa10

    r8 0x0 0

    r9 0x0 0

    r10 0x0 0

    r11 0x0 0

    r12 0x0 0

    r13 0x0 0

    r14 0x0 0

    r15 0x0 0

    rip 0x100000fde 0x100000fde <main+5>

    eflags 0x302 [ TF IF ]

    cs 0x2b 43

    ss

    ds

    es

    fs 0x0 0

    gs 0x0 0

    fs_base

    gs_base

    可以看到rax已经变成了汇编代码#16行传入的0x2000004。

    可以使用指令来查看某一寄存器的值,如:

    (gdb) print/x $rax

    $1 = 0x2000004

    同样,可以使用指令查看内存的值:

    (gdb) x/17xb 0x100000fd9

    0x100000fd9 <main>: 0xb8 0x04 0x00 0x00 0x02 0xbf 0x01 0x00

    0x100000fe1 <main+8>: 0x00 0x00 0x48 0xbe 0x00 0x10 0x00 0x00

    0x100000fe9 <main+16>: 0x01

    指令用法会在后面提到。

    接下来就调试或者直接运行结束即可,这里直接运行结束:

    (gdb) continue

    Continuing.

    HelloWorld!

    [Inferior 1 (process 8812) exited normally]

    4.GDB中命令x的用法

    命令 x :查看内存地址中的值

    格式:x/<n/f/u> <addr>

    n:正整数,表示需要显示的内存单元的个数,即从当前地址向后显示n个内存单元的内容,一个内存单元的大小由第三个参数u定义。

    f:addr指向的内存内容的输出格式,s对应输出字符串,此处需特别注意输出整型数据的格式:

    x 按十六进制格式显示变量d 按十进制格式显示变量u 按十六进制格式显示无符号整型o 按八进制格式显示变量t 按二进制格式显示变量a 按十六进制格式显示变量c 按字符格式显示变量f 按浮点数格式显示变量

    u:以多少个字节作为一个内存单元,默认为4。当然u还可以用被一些字符表示,如b=1 byte, h=2 bytes,w=4 bytes,g=8 bytes.

    <addr>:内存地址。

    整合这个命令的诠释:就是以addr为起始地址,返回n个单元的值,每个单元对应u个字节,输出格式是f。

    如:x/17xb 0x100000fd9 表示:以地址0x100000fd9为起始地址,返回17个单元的值,每个单元有一个字节,输出格式为无符号十六进制。

    也就是说返回了17个字节的数据,以十六进制输出,这17个字节的数据,每一个字节为一个单元输出,共输出17个单元。

    (gdb) x/17xb 0x100000fd9

    0x100000fd9 <main>: 0xb8 0x04 0x00 0x00 0x02 0xbf 0x01 0x00

    0x100000fe1 <main+8>: 0x00 0x00 0x48 0xbe 0x00 0x10 0x00 0x00

    0x100000fe9 <main+16>: 0x01

    以字节编址,故0x100000fd9为0xb8,0x100000fda为0x04等。

    5.macOS的系统调用(Syscall)

    可以看到在上面的汇编代码中用了两个系统调用,比如0x2000004表示 syscall 调用号 write,这个Apple已经在这里开源了macOS所有的系统调用表,在里面可以看到write函数的系统调用:

    4 AUE_NULL ALL { user_ssize_t write(int fd, user_addr_t cbuf, user_size_t nbyte); }

    可是这里系统调用号是4而不是我们在代码里面所写的0x2000004,在这里Apple也给出了解释,再通俗的说一下。

    在macOS系统中,所有系统调用都通过syscall指令进入系统内核,但是macOS有不同类型的系统调用,比如Mach系统调用,BSD系统调用等等。严格定义如下:

    #define SYSCALL_CLASS_NONE 0 /* Invalid */ #define SYSCALL_CLASS_MACH 1 /* Mach */ #define SYSCALL_CLASS_UNIX 2 /* Unix/BSD */ #define SYSCALL_CLASS_MDEP 3 /* Machine-dependent */ #define SYSCALL_CLASS_DIAG 4 /* Diagnostics */

    上面的标记称为系统调用的类枚举标记。

    macOS系统中有一个SYSCALL_CLASS_SHIFT,就是将系统调用的类枚举标记左移24位得到的值,所以进行不同的系统调用需要加上那个值。

    所以,我们进行BSD类型的系统调用需要2<<24+4就是0x2000004。

    Processed: 0.010, SQL: 9