assembly - Printf 显示垃圾值

标签 assembly x86 printf nasm libc

section .data

    array dw  1,2,3,4,5,6,7,8,9,10   ; array of integers
    msg db " numbers are :  %d %d ",10,0

section .text 

global main
extern printf   ; for c printf

main: 

    push ebp
    mov ebp,esp     ;intialise stack

    mov ax,11
    push ax        ;push ax with value 11

    mov ax,22
    push ax      ;push ax with value 12
    push msg
    call printf     ; calling printf function

    add esp ,12

    mov esp,ebp     ;restore stack
    pop ebp

当我推送即时值而不是通过AX推送时,它工作得很好。这是为什么?

最佳答案

正如 Jester 指出的那样:不要使用 32 位代码将 16 位值压入堆栈,除非您知道自己在做什么。 AX 是一个 16 位寄存器(32 位 EAX 寄存器的下半部分)。当你这样做时:

push ax

16 位被压入堆栈,因为 AX 是一个 16 位寄存器。这将阻止printf由于数据不是 32 位宽,因此无法正确访问该值。如果你要这样做:

push 11

你会发现这很有效。当NASM生成32位代码时,它假设立即值被压入堆栈时是32位宽。这就是为什么这个场景对你有用。

如果您要PUSH一个32位寄存器,那么完整的32位数据将被放置在堆栈顶部。举个例子:

push eax

看来您的意图可能是访问或遍历 WORD 数组(WORD = 16 位值)并使用 %d 打印它们。 printf 使用的转换说明符。 %d将 32 位 DWORDS 打印为有符号值。在将它们压入堆栈之前,您必须将它们作为 WORD 加载到内存中,并将它们转换为 DWORDS

汇编语言没有高级编程语言传统意义上的变量概念。您可以为保存WORD(16 位值)的内存位置赋予含义。它是否已签名或未签名取决于您用于与该数据交互的代码。

386 有两条指令可以提供帮助。 MOVSX用于符号扩展较小的操作数到较大的操作数。当您希望保留SIGN(正数或负数)时使用此选项。 MOVZX用于将较小的操作数零扩展为较大的操作数。该指令适用于无符号值,在转换过程中只是将目标操作数的所有高位设置为零。

作为一个例子,我编写了一些围绕单词数组的代码:

section .data
    array dw  -1,0,1,2,3,4,5,6,7,8,9,10,-32768,32767,32768
                                     ; array of integers
    arraylen equ ($-array)/2         ; number of word elements in array
    msg db " numbers are :  %d %d ",10,0

section .text

global main
extern printf          ; for c printf

main:

    push ebp
    mov ebp,esp        ; intialise stack
    push ebx           ; ebx is caller saved register. We destroy it so
                       ;     we must restore it before our function exits

    xor ebx, ebx       ; index = 0

    ; Make the equivalent of a for loop to traverse array
.loop1:
    cmp ebx, arraylen  ; We'll process all the elements of the array
    je .endloop        ; End when our index = arraylen

    movzx eax, word [array + ebx * 2] ; Use EBX as index into WORD array
                       ; zero extend 16-bit array value into 32-bit register
    push eax           ; parameter 3 = unsigned DWORD
    movsx eax, word [array + ebx * 2] ; Use EBX as index into WORD array
                       ; sign extend 16-bit array value into 32-bit register
    ; movsx eax, ax    ; The line above would have also worked this way
    push eax           ; parameter 2 = signed DWORD onto stack

    push msg           ; parameter 1 = pointer to format string
    call printf        ; calling printf function
    add esp, 12

    inc ebx            ; index += 1
    jmp .loop1         ; continue for loop

.endloop:

    pop ebx            ; Restore ebx
    mov esp,ebp        ; restore stack
    pop ebp

该代码确保根据CDECL calling convention被调用者保存的任何寄存器(上面代码中的EBX)在函数开始和结束时保存和恢复。更多关于这方面的解释可以在最近的一篇StackOverflow answer中找到。我写的。

我编写了等效的 for 循环(您可以将其编码为 do-while)或任何其他循环构造来遍历数组。我同时使用 MOVZXMOVSX 并使用您的 printf 显示结果格式字符串。

注意::MOVZX也可以通过将目标操作数清零并随后将源操作数移至目标来完成。举个例子:

movzx eax, word [array + ebx * 2]

可以编码为:

xor eax, eax  ; eax = 0
mov ax, word [array + ebx * 2]

人们应该能够组装和链接:

nasm -f elf32 testmov.asm
gcc -m32 -o testmov testmov.o

当运行为./testmov时结果应如下所示:

numbers are :  -1 65535
numbers are :  0 0
numbers are :  1 1
numbers are :  2 2
numbers are :  3 3
numbers are :  4 4
numbers are :  5 5
numbers are :  6 6
numbers are :  7 7
numbers are :  8 8
numbers are :  9 9
numbers are :  10 10
numbers are :  -32768 32768
numbers are :  32767 32767
numbers are :  -32768 32768

如果您想使用 printf 打印无符号 WORD(16 位值)您可以使用%hu (无符号短),对于有符号的WORD,您可以使用 %hd (缩写)。尽管您仍然必须为参数传递DWORD,但您无需担心零扩展(或符号扩展),因为 printf只会查看作为参数传递的 DWORD 的低 2 个字节。

关于assembly - Printf 显示垃圾值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36132676/

相关文章:

汇编 - Mov 指令和操作数大小

Windows 初始执行上下文

Java乘法优化

c - 在 C 中写入多个文件并迭代其名称

gcc - 在哪里可以找到包含 printf 函数定义的目标文件?

c++ - Freertos 硬故障分析——堆栈寄存器

assembly - vzeroall零寄存器ymm16到ymm31吗?

程序集 32 位保护模式,标签未指向定义的字符串?

linux - 如何在禁用 SMP 支持的情况下运行 "invd"指令?

c - fprintf 没有按照我想要的方式工作