C使用NASM指向EFLAGS

Hel*_*mid 3 c x86 pointers nasm eflags

对于我学校的任务,我需要编写一个C程序,它使用汇编程序进行16位加法.除了结果,EFLAGS也将被退回.

这是我的C程序:

int add(int a, int b, unsigned short* flags);//function must be used this way
int main(void){
    unsigned short* flags = NULL;
    printf("%d\n",add(30000, 36000, flags);// printing just the result
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

目前程序只打印出结果而不是标志,因为我无法得到它们.

这是我使用NASM的汇编程序:

global add
section .text

add:
    push ebp    
    mov ebp,esp
    mov ax,[ebp+8]
    mov bx,[ebp+12]
    add ax,bx
    mov esp,ebp
    pop ebp
    ret
Run Code Online (Sandbox Code Playgroud)

现在一切顺利.但我不知道如何获得必须[ebp+16]指向标志寄存器的指针.教授说我们将不得不使用这个pushfd命令.

我的问题只在于汇编代码.在得到标志的解决方案后,我将修改C程序以发出标志.

Pet*_*des 5

通常,您只需使用调试器来查看标志,而不是编写所有代码以将它们转换为C变量以进行调试打印.特别是因为体面的调试器会为您象征性地解码条件标志,而不是显示十六进制值.

您不必知道或关心FLAGS中的哪个位是CF,哪个是ZF.(此信息是不相关的写实时节目,无论是.我没有记住它,我才知道这标志由像不同的条件下进行测试jaejl.当然,这是很好的了解,标志只是数据,你可以复制,保存/恢复,甚至修改,如果你想)


您的函数args和返回值是int,在您使用的System V 32位x86 ABI中为32位.(链接到标签wiki 中的ABI文档).编写一个仅查看其输入的低16位的函数,并在输出的高16位中留下高垃圾是一个错误.int原型中的返回值告诉编译器EAX的所有32位都是返回值的一部分.

正如迈克尔指出的那样,你似乎在说你的任务要求使用16位ADD.与查看完整的32位相比,这将产生具有不同输入的进位,溢出和其他条件.(顺便说一句,这篇文章很好地解释了携带与溢出.)

这就是我要做的.请注意ADD的32位操作数大小.

global add
section .text

add:
    push  ebp    
    mov   ebp,esp      ; stack frames are optional, you can address things relative to ESP

    mov   eax, [ebp+8]    ; first arg: No need to avoid loading the full 32 bits; the next insn doesn't care about the high garbage.
    add   ax,  [ebp+12]   ; low 16 bits of second arg.  Operand-size implied by AX

    cwde                  ; sign-extend AX into EAX

    mov   ecx, [ebp+16]   ; the pointer arg
    pushf                 ; the simple straightforward way
    pop   edx
    mov   [ecx], dx       ; Store the low 16 of what we popped.  Writing  word [ecx]  is optional, because dx implies 16-bit operand-size
                          ; be careful not to do a 32-bit store here, because that would write outside the caller's object.

    ;  mov esp,ebp   ; redundant: ESP is still pointing at the place we pushed EBP, since the push is balanced by an equal-size pop
    pop ebp
    ret
Run Code Online (Sandbox Code Playgroud)

CWDE(8086 CBW指令的16-> 32形式)不要与CWD(AX - > DX:AX 8086指令)混淆.如果你没有使用AX,那么MOVSX/MOVZX是一个很好的方法.


有趣的方式:我们可以直接在目标内存地址中执行16位弹出操作,而不是使用默认操作数大小并执行32位推送和弹出操作.这会使堆栈失去平衡,所以我们可以mov esp, ebp再次取消注释,或者使用16位pushf(带有操作数大小的前缀,根据文档使其仅推送低16 FLAGS,而不是32位EFLAGS .)

; What I'd *really* do: maximum efficiency if I had to use the 32-bit ABI with args on the stack, instead of args in registers
global add
section .text

add:
    mov   eax, [esp+4]    ; first arg, first thing above the return address
    add   ax,  [esp+8]    ; second arg
    cwde                  ; sign-extend AX into EAX

    mov   ecx, [esp+12]   ; the pointer

    pushfw                     ; push the low 16 of FLAGS
    pop     word [ecx]         ; pop into memory pointed to by unsigned short* flags

    ret
Run Code Online (Sandbox Code Playgroud)

PUSHFW和POP WORD都将使用操作数大小前缀进行汇编.输出来自objdump -Mintel,使用与NASM略有不同的语法:

  4000c0:       66 9c                   pushfw 
  4000c2:       66 8f 01                pop    WORD PTR [ecx]
Run Code Online (Sandbox Code Playgroud)

PUSHFW与之相同o16 PUSHF.在NASM中,o16应用操作数大小前缀.


如果您只需要低8个标志(不包括OF),您可以使用LAHF将FLAGS加载到AH中并存储它.


推送直接进入目的地不是我推荐的.暂时将堆栈指向某个随机地址通常是不安全的.带信号处理程序的程序将异步使用堆栈下方的空间.这就是为什么你必须在使用它之前保留堆栈空间的原因sub esp, 32,即使你不打算通过在堆栈上推送更多内容来进行覆盖它的函数调用.唯一的例外是你有一个.


你C来电者:

你正在传递一个NULL指针,所以当然你的asm函数是segfaults.传递一个本地的地址给函数某处存储.

int add(int a, int b, unsigned short* flags);

int main(void) {
    unsigned short flags;
    int result = add(30000, 36000, &flags);
    printf("%d %#hx\n", result, flags);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)