3

对于我学校的一项任务,我需要编写一个使用汇编程序进行 16 位加法的 C 程序。除了结果之外,还应返回 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;
}

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

这是我使用 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

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

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

4

2 回答 2

5

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

您不必知道或关心 FLAGS 中的哪个位是 CF,哪个是 ZF。(此信息也与编写实际程序无关。我没有记住它,我只知道哪些标志是通过不同条件测试的jaejl。当然,最好了解 FLAGS 只是您可以复制,保存/恢复,甚至根据需要进行修改)


您的函数 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

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

PUSHFW 和 POP WORD 都将使用操作数大小的前缀进行组合。的输出objdump -Mintel,它使用与 NASM 略有不同的语法:

  4000c0:       66 9c                   pushfw 
  4000c2:       66 8f 01                pop    WORD PTR [ecx]

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


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


我不建议直接推入目的地。暂时将堆栈指向某个随机地址通常是不安全的。带有信号处理程序的程序将异步使用堆栈下方的空间。这就是为什么你必须在使用它之前保留堆栈空间sub esp, 32或其他任何东西,即使你不打算通过将更多东西压入堆栈来进行覆盖它的函数调用。唯一的例外是当您有一个时。


你 C 来电者:

您正在传递一个 NULL 指针,因此您的 asm 函数当然会出现段错误。传递本地地址以将函数提供给某个地方进行存储。

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;
}
于 2016-09-13T16:07:59.847 回答
0

这只是一种简单的方法。我没有测试它,但你应该明白......

只需设置ESP指针值,将其递增 2(即使对于 32 位拱门),PUSHF如下所示:

global add
section .text

add:
    push ebp    
    mov ebp,esp
    mov ax,[ebp+8]
    mov bx,[ebp+12]
    add ax,bx
    ; --- here comes the mod
    mov esp, [ebp+16]      ; this will set ESP to the pointers address "unsigned short* flags"
    lea esp, [esp+2]       ; adjust ESP to address above target
    db 66h                 ; operand size prefix for 16-bit PUSHF (alternatively 'db 0x66', depending on your assembler
    pushf                  ; this will save the lower 16-bits of EFLAGS to WORD PTR [EBP+16] = [ESP+2-2]
    ; --- here ends the mod
    mov esp,ebp
    pop ebp
    ret

这应该可行,因为 PUSHF 递减ESP2,然后将值保存到WORD PTR [ESP]. 因此,在使用指针地址之前必须增加它。设置ESP为适当的值使您能够命名 的直接目标PUSHF

于 2016-09-13T14:57:10.217 回答