NASM-宏本地标签作为另一个宏的参数 [英] NASM - Macro local label as parameter to another macro

查看:131
本文介绍了NASM-宏本地标签作为另一个宏的参数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用宏(如本教程所示, )来打印字符串.宏PRINT创建用于定义字符串内容(str)和长度(strlen)的本地标签,然后将它们作为参数传递给进行系统调用的第二个宏_syscall_write.

但是运行代码失败,并且我收到一条Segmentation fault (core dumped)消息.

我怀疑问题出在这几行,但我不明白为什么.

mov rsi, %1  ; str
mov rdx, %2  ; strln

这是完整的代码:

%macro PRINT 1

    ; Save state
    push rax
    push rdi
    push rsi
    push rdx

    %%str    db  %1, 0       ; arg0 + null terminator
    %%strln  equ $ - %%str   ; current position - string start

    ; Write
    _syscall_write %%str, %%strln

    ; Restore state
    pop rdx
    pop rsi
    pop rdi
    pop rax

%endmacro

%macro _syscall_write 2
    mov rax, 1
    mov rdi, 1
    mov rsi, %1  ; str
    mov rdx, %2  ; strln
    syscall
%endmacro


global _start


section .data

    SYS_EXIT   equ 60
    EXIT_CODE  equ 0


section .text

    _start:

        PRINT "Hello World!"


    exit:

        mov rax, SYS_EXIT
        mov rdi, EXIT_CODE
        syscall


这是目标文件的反汇编(从带有push/pop注释的版本开始).

看着扩展的代码,我仍然看不到什么地方出了问题.字节0x0..0xC看起来像乱码,但与Hello World!中字符的ASCII码相对应.在对sys_write进行syscall之前,raxrdi似乎收到了预期的0x1值,rsi0x0值指向了字符串开头,而rdx的值则0xd这是字符串长度(12 +1)...

Disassembly of section .text:

0000000000000000 <_start>:
   0:   48                      rex.W
   1:   65                      gs
   2:   6c                      ins    BYTE PTR es:[rdi],dx
   3:   6c                      ins    BYTE PTR es:[rdi],dx
   4:   6f                      outs   dx,DWORD PTR ds:[rsi]
   5:   20 57 6f                and    BYTE PTR [rdi+0x6f],dl
   8:   72 6c                   jb     76 <SYS_EXIT+0x3a>
   a:   64 21 00                and    DWORD PTR fs:[rax],eax

   d:   b8 01 00 00 00          mov    eax,0x1
  12:   bf 01 00 00 00          mov    edi,0x1
  17:   48 be 00 00 00 00 00    movabs rsi,0x0
  1e:   00 00 00
  21:   ba 0d 00 00 00          mov    edx,0xd
  26:   0f 05                   syscall

0000000000000028 <exit>:
  28:   b8 3c 00 00 00          mov    eax,0x3c
  2d:   bf 00 00 00 00          mov    edi,0x0
  32:   0f 05                   syscall

解决方案

rex.W gs ins是特权指令,并且在用户空间中出错.这是程序的第一条指令,是在不更改节的情况下在宏中扩展%%str db %1, 0的结果.

不要将数据放在将要作为指令执行的位置;将section .rodata用于只读数据.

GAS允许您执行.pushsection .rodata/.popsection来在任何部分内正确扩展宏,但是对于NASM,我不确定我们是否可以做得比在数据后无条件切换到section .text更好. >

NASM预处理程序具有 %push [optional context-name]/%pop 要保存/还原预处理程序上下文,例如用于嵌套重复直到预处理器的内容.但这仅适用于预处理程序,不包括还原旧的section.

%macro PRINT 1
  ...
section .rodata 
    %%str    db  %1, 0       ; arg0 + null terminator
    %%strln  equ $ - %%str   ; current position - string start
section .text
  ... rest of the macro

因此,在使用宏之后,您将无条件地位于.text部分,而不是.text.cold或任何其他自定义部分.


还要注意,equ指令并不关心它们位于哪个节中(除非它们在定义中使用$).因此,strln必须与str位于同一部分,但SYS_EXITsection .data无关.这是一个汇编时间常量,使用时会变成立即数.


mov r64, imm64是将绝对地址放入寄存器的低效率方法.它需要在PIE可执行文件中进行加载时修复,并且比与位置无关的 lea rsi, [rel %%str] 长. NASM将mov rsi, str组装为10字节的mov r64, imm64,而YASM使用mov r/m64, sign_extended_imm32(甚至在PIE可执行文件中也不起作用). https://nasm.us/doc/nasmdo11.html#section-11.2

您也许可以编写一个使用%ifidn与字符串相同的条件来检查rsi作为字符串arg的宏,并且在这种情况下不执行任何操作(指针已经在RSI中),否则请使用lea rsi, [rel %%str].但是,这对于内存中的指针不起作用,而mov rsi, [rbx]会在其中工作.取决于您希望宏的样式.您可能会%if一个条件,该条件在arg字符串中查找[并使用mov而不是lea.


如果要保存/恢复所有破坏寄存器,请记住,syscall本身破坏RCX(保存的RIP)和R11(保存的RFLAGS).

通常,您只需要注册一个注册宏Clobbers的文档即可;这些都是x86-64 System V中的调用密集型寄存器.但是,如果要使用调试打印宏,则可能希望它保存/恢复所有内容?除push/pop以外,破坏RSP下方的红色区域.我认为我从来没有在asm中使用过调试打印功能,只是通过调试器设置断点,然后单击继续"以查看下一个断点.或者只是单步执行并观察寄存器值的变化,例如与GDB的layout reg.

I am trying to use a macro (as shown in this tutorial) to print a string. The macro PRINT creates local labels to define the string content (str) and length (strlen), and then passes these as parameters to a second macro _syscall_write which makes the syscall.

However running the code fails and I get a Segmentation fault (core dumped) message.

I suspect the problem to be this particular lines, but I don't understand why.

mov rsi, %1  ; str
mov rdx, %2  ; strln

Here is the full code:

%macro PRINT 1

    ; Save state
    push rax
    push rdi
    push rsi
    push rdx

    %%str    db  %1, 0       ; arg0 + null terminator
    %%strln  equ $ - %%str   ; current position - string start

    ; Write
    _syscall_write %%str, %%strln

    ; Restore state
    pop rdx
    pop rsi
    pop rdi
    pop rax

%endmacro

%macro _syscall_write 2
    mov rax, 1
    mov rdi, 1
    mov rsi, %1  ; str
    mov rdx, %2  ; strln
    syscall
%endmacro


global _start


section .data

    SYS_EXIT   equ 60
    EXIT_CODE  equ 0


section .text

    _start:

        PRINT "Hello World!"


    exit:

        mov rax, SYS_EXIT
        mov rdi, EXIT_CODE
        syscall


Here is a disassembly of the object file (from a version with the push/pop commented out).

Looking at the expanded code I still cannot see what is wrong. The bytes 0x0..0xC look like gibberish but correspond to the ascii code of the characters in Hello World!. Before the syscall to sys_write, rax and rdi seem to receive the expected value of 0x1, rsi the value of 0x0 which points to the string start, and rdx the value of 0xd which is the string length (12 + 1)...

Disassembly of section .text:

0000000000000000 <_start>:
   0:   48                      rex.W
   1:   65                      gs
   2:   6c                      ins    BYTE PTR es:[rdi],dx
   3:   6c                      ins    BYTE PTR es:[rdi],dx
   4:   6f                      outs   dx,DWORD PTR ds:[rsi]
   5:   20 57 6f                and    BYTE PTR [rdi+0x6f],dl
   8:   72 6c                   jb     76 <SYS_EXIT+0x3a>
   a:   64 21 00                and    DWORD PTR fs:[rax],eax

   d:   b8 01 00 00 00          mov    eax,0x1
  12:   bf 01 00 00 00          mov    edi,0x1
  17:   48 be 00 00 00 00 00    movabs rsi,0x0
  1e:   00 00 00
  21:   ba 0d 00 00 00          mov    edx,0xd
  26:   0f 05                   syscall

0000000000000028 <exit>:
  28:   b8 3c 00 00 00          mov    eax,0x3c
  2d:   bf 00 00 00 00          mov    edi,0x0
  32:   0f 05                   syscall

解决方案

rex.W gs ins is a privileged instruction, and faults in user-space. This is the first instruction of your program, from the expansion of %%str db %1, 0 in your macro without changing sections.

Don't put data where it will be executed as instructions; use section .rodata for read-only data.

GAS would let you do .pushsection .rodata / .popsection to expand the macro correctly inside any section, but for NASM I'm not sure if we can do better than unconditionally switch to section .text after the data.

The NASM preprocessor has %push [optional context-name] / %pop to save/restore preprocessor context, e.g. for nested repeat-until preprocessor stuff. But that's only for the preprocessor, and doesn't include restoring the old section.

%macro PRINT 1
  ...
section .rodata 
    %%str    db  %1, 0       ; arg0 + null terminator
    %%strln  equ $ - %%str   ; current position - string start
section .text
  ... rest of the macro

So after using the macro, you're unconditionally in the .text section, not in .text.cold, or whatever other custom section.


Also note that equ directives don't care what section they're in (unless they use $ in their definition). So strln needs to be in the same section as str, but SYS_EXIT has nothing to do with section .data. It's an assemble-time constant that turns into an immediate when you use it.


mov r64, imm64 is an inefficient way to put an absolute address in a register. It needs a load-time fixup in a PIE executable, and is longer than position-independent lea rsi, [rel %%str]. NASM assembles mov rsi, str into 10-byte mov r64, imm64, while YASM uses mov r/m64, sign_extended_imm32 (which doesn't even work in a PIE executable). https://nasm.us/doc/nasmdo11.html#section-11.2

You could maybe write a macro that uses %ifidn string-identical condition to check for rsi as the string arg, and in that case do nothing (the pointer is already in RSI), otherwise use lea rsi, [rel %%str]. That won't work for a pointer in memory, where mov rsi, [rbx] would have worked, though. Depends how fancy you want your macro to be. You could maybe %if a condition that looked for [ in the arg string and use mov instead of lea.


If you want to save/restore all the registers you clobber, remember that syscall itself clobbers RCX (saved RIP) and R11 (saved RFLAGS).

Normally you'd just document which registers a macro clobbers; those are all call-clobbered registers in x86-64 System V. But if you want a debug-print macro, you probably want it to save/restore everything? Except push/pop destroy the red-zone below RSP. I don't think I've ever used debug-prints in asm, just setting breakpoints with a debugger and hitting "continue" to see which breakpoint is hit next. Or just single-step and watch register values change, e.g. with GDB's layout reg.

这篇关于NASM-宏本地标签作为另一个宏的参数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆