Printf显示垃圾值

时间:2016-03-21 13:43:52

标签: assembly x86 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 推送时,它工作正常。那是为什么?

2 个答案:

答案 0 :(得分:4)

正如Jester指出的那样:除非你知道自己在做什么,否则不要将16位值用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)或任何其他循环结构来遍历数组。我同时使用 MOVZX MOVSX ,并以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只会查看较低的2个字节您作为参数传递的 DWORD 的内容。

答案 1 :(得分:0)

您可以将16位寄存器推送到32位堆栈,但printf稍后会将其从堆栈中取出为完整的32位项目。这就是你获得垃圾值的地方 - printf拾取的物品的较高部分。这正是你的问题。因此,为了防止将来出现问题,请始终将32位CPU,64位项目上的32位项目推送到64位CPU,依此类推。对于立即,您的编译器将默认为32位立即,这就是您的程序与immediates一起工作的原因。