Assembly Printf显示垃圾值

Assembly Printf显示垃圾值,assembly,x86,printf,nasm,libc,Assembly,X86,Printf,Nasm,Libc,当我推送即时值而不是通过AX推送时,效果很好。为什么会这样?正如Jester所指出的:除非您知道自己在做什么,否则不要用32位代码将16位值推送到堆栈上。AX是一个16位寄存器(32位EAX寄存器的下半部分)。当您这样做时: 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

当我推送即时值而不是通过AX推送时,效果很好。为什么会这样?

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

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
16位被推送到堆栈上,因为AX是一个16位寄存器。这将阻止
printf
正确访问该值,因为数据不是32位宽。如果你要做:

push ax
你会发现这很管用。当NASM生成32位代码时,它假设立即值在推送到堆栈上时为32位宽。这就是为什么这个场景对你有效

如果要推送一个32位寄存器,那么完整的32位数据将放在堆栈顶部。例如:

push 11
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

您的目的可能是访问或遍历单词数组(WORD=16位值),并使用
%d
转换说明符(由
printf
使用)打印它们<代码>%d将32位DWORD打印为带符号的值。您必须将它们作为单词加载到内存中,并将它们转换为DWORD,然后才能将它们推送到堆栈上

汇编语言没有高级编程语言传统意义上的变量概念。您可以赋予包含单词(16位值)的内存位置意义。它是有符号的还是无符号的取决于用于与该数据交互的代码

386有两个帮助说明。用于将较小的操作数扩展到较大的操作数。当您希望保留符号(正或负)时,将使用此选项。用于将较小的操作数零扩展到较大的操作数。此指令用于无符号值,在转换过程中仅将目标操作数的所有高位设置为零

作为一个例子,我写了一些以单词数组为中心的代码:

push eax
该代码确保在函数开始和结束时保存并还原根据保存的被调用方的任何寄存器(上面代码中的EBX)。关于这一点的更多解释可以在我最近写的一篇文章中找到

我对for循环(您可以将其编码为do while)或任何其他循环构造的等价物进行编码,以遍历数组。我同时使用MOVZX和MOVSX,并使用
printf
格式字符串显示结果

注意::MOVZX也可以通过将目标操作数调零并在调零后将源操作数移到目标操作数来完成。例如:

push 11
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
可能被编码为:

movzx eax, word [array + ebx * 2]

一个人应该能够组装和连接:

xor eax, eax  ; eax = 0
mov ax, word [array + ebx * 2]
当作为
/testmov
运行时,结果应如下所示:



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

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

您能给我们举一个代码失败的例子吗?;)为什么在使用
%d
(对应于
int
)时要推16位寄存器?实际上,无论格式字符串是什么,都不应该推16位寄存器(除非您真的知道自己在做什么)。此外,您正确地使用了
添加esp,12
,并且假设推了12个字节。TL;DR:在任何地方都使用
eax
。无需向
esp
添加任何内容,因为下面的指令
mov esp、ebp
会还原它。