【问题标题】:Printf in Nasm behaviorNasm 行为中的 Printf
【发布时间】:2012-03-28 23:03:59
【问题描述】:

如果能解释一下使用 printf、nasm 和 gcc 编译的以下示例中发生了什么,我将不胜感激。 为什么“sud”只印在屏幕上?我也不明白,为什么当我将“push 'sud'”与“push 'sudo'”交换时,屏幕上会显示“sudobor”? 有人可以解释一下为什么我需要推动 esp 吗?它是一个空值,它必须位于 printf 中字符串的末尾吗? 提前谢谢。

这是 string.s 文件:

section .data

section .text
  global start
  extern printf

start:
     push    ebp           
     mov     ebp, esp  
     push 'bor'
     push 'sud'
     push esp
     call printf
     mov     esp, ebp
     pop     dword ebp

  ret

这是c文件:

#include <stdio.h>
#include <stdlib.h>
extern void start();
int main(void) {
  start();

}

【问题讨论】:

    标签: printf nasm


    【解决方案1】:

    首先,感谢您让我大吃一惊。当我第一次查看您的代码时,我根本不相信它会起作用。然后我尝试了它并复制了你的结果。现在它对我来说很有意义,尽管是以一种扭曲的方式。 :-) 我会试着解释一下。

    首先,让我们看看实现这一目标的更明智的方法。在 ASM 文件的数据部分定义一个字符串:

    section .data
    string: db "Hey, is this thing on?", 0
    

    然后在调用 printf 之前将该字符串的地址压入堆栈:

    push string
    call printf
    

    因此,printf 的第一个参数(调用前压入堆栈的最后一个参数)是指向格式字符串的指针。您的代码所做的是将字符串压入堆栈,然后是指向该字符串的堆栈指针。

    接下来,我将替换你的字符串,以便在反汇编中更容易跟踪:

    push '567'
    push '123'
    push esp
    call printf
    

    用nasm组装,然后用objdump反汇编:

    nasm string.s -f elf32 -o string.o
    objdump -d -Mintel string.o
    

    当您按下时,例如“123”,它会转换为 32 位十六进制数字——在这种情况下为 0x333231。请注意,完整的 32 位是 0x00333231。

    3:  68 35 36 37 00          push   0x373635
    8:  68 31 32 33 00          push   0x333231
    d:  54                      push   esp
    

    压入堆栈会减少堆栈指针。假设初始堆栈指针为 0x70(为简单起见),这是调用 printf 之前堆栈的状态:

    64:         68:         6c:         70:
    68 00 00 00 31 32 33 00 35 36 37 00 ...
    

    因此,当调用 print 时,它使用第一个参数作为字符串指针并开始打印字符,直到看到 NULL (0x00)。

    这就是为什么这个例子只打印“123”(你原来的“sud”)。

    所以让我们推送“1234”而不是“123”。这意味着我们正在推动值 0x34333231。调用 printf 时,堆栈现在看起来像:

    64:         68:         6c:         70:
    68 00 00 00 31 32 33 34 35 36 37 00 ...
    

    现在堆栈上的这 2 个字符串之间没有 NULL 间隙,此示例将打印“1234567”(或原始字符串中的“sudobor”)。

    含义:尝试推送“5678”而不是“567”。您可能会遇到分段错误,因为 printf 只会继续读取要打印的字符,直到它尝试读取它没有读取权限的内存。另外,尝试推送一个长度超过 4 个字符的字符串(例如,“push '12345'”)。汇编器不会让您这样做,因为它无法将其转换为 32 位数字。

    【讨论】:

    • 你需要空终止字符串吗?
    • @Jeff:我想我必须明确添加 NULL。但是,上面的代码有效。也许在正确的地方还有另一个 NULL,纯属巧合。
    • 作为测试,我会在第一个字符串之后立即声明另一个字符串,看看它是否仍然按预期工作。我会包含空终止符,但不知道它是否需要。
    • 谢谢,杰夫!我刚刚测试了它,你是对的——字符串确实需要明确地以 NULL 结尾。我已经更新了答案。
    • 感谢您提供如此令人难以置信和彻底的回答:)
    猜你喜欢
    • 1970-01-01
    • 2020-12-09
    • 1970-01-01
    • 2016-01-25
    • 1970-01-01
    • 2019-08-02
    • 2015-06-29
    • 2014-12-27
    • 2013-12-14
    相关资源
    最近更新 更多