扫描程序集中的字符指针



所以我有一项任务要做,这需要我在汇编中scanfchar*。我尝试了以下代码:

.data
INPUT_STRING:   .string "Give me a string: "
SCANF_STRING:   .string "%s"
PRINTF_STRING:  .string "String: %sn"
.text
.globl main
.type main, @function
main:
leal 4(%esp), %ecx
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
movl %esp, %ebp
pushl %ecx
subl $32, %esp
pushl $INPUT_STRING 
call printf #printf("Give me a string: ")
addl $4, %esp
pushl -12(%ebp) # char*
pushl $SCANF_STRING # "%s"
call scanf scanf("%s", char*)
addl $8, %esp    
pushl -12(%ebp)
pushl PRINTF_STRING
call printf #printf("String: %sn")
addl $16, %esp
movl -4(%ebp), %ecx   
xorl %eax, %eax
leave
leal -4(%ecx), %esp
ret

它首先正确写下 printf,然后等待输入(所以scanf工作),但是当我输入任何东西时 ->Segmentation fault.

我知道,应该以某种方式初始化char*,但是如何从程序集级别执行此操作?

我正在 Manjaro 64 位上编译它,gcc -m32

GCC在进入main时的堆栈对齐代码过于复杂:

leal 4(%esp), %ecx
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
movl %esp, %ebp
pushl %ecx
subl $32, %esp
...
leave
leal -4(%ecx), %esp
ret

这样做:

pushl %ebp
movl %esp, %ebp
subl $32, %esp        # Space for 32 local bytes
andl $-16, %esp       # Alignment by 16
...
leave
ret

现代Linux上使用的i386 System V ABI版本确实保证/要求在call之前对齐16字节的堆栈对齐,因此您可以重新对齐3次推送(包括push %ebp)而不是and。 与 x86-64 不同,大多数 i386 库函数不会被编译为在其堆栈空间中的局部变量上使用movapsmovdqa16 字节对齐的加载/存储,因此您通常可以像在扫描之前对PUSHes 所做的那样取消对齐堆栈。 (不过,当你第一次call printfESP % 16 == 0;这是正确的。


您希望对字符串使用 12 个字节的本地堆栈帧。scanf需要这 12 个字节的起始地址。该区域的地址在编译时未知。-12(%ebp)为您提供此地址的值,而不是地址本身。LEA是计算地址的指令。因此,您必须插入此指令以在运行时获取地址并将其传递给 C 函数:

leal -12(%ebp), %eax
pushl %eax # char*

这是工作示例(小错误也已更正):

.data
INPUT_STRING:   .string "Give me a string: "
SCANF_STRING:   .string "%11s"      ##### Accept only 11 characters (-1 because terminating null)
PRINTF_STRING:  .string "String: %sn"
.text
.globl main
.type main, @function
main:
pushl %ebp
movl %esp, %ebp
subl $32, %esp
mov $32, %ecx
mov %esp, %edi
mov $88, %al
rep stosb
pushl $INPUT_STRING
call printf                         # printf("Give me a string: ")
addl $4, %esp
leal -12(%ebp), %eax
pushl %eax                          # char*
pushl $SCANF_STRING                 # "%s"
call scanf                          # scanf("%s", char*)
addl $8, %esp
leal -12(%ebp), %eax
pushl %eax                          # char*
pushl $PRINTF_STRING            ##### '$' was missing
call printf                         # printf("String: %sn")
addl $8, %esp                   ##### 16 was wrong. Only 2 DWORD à 4 bytes were pushed
leave
ret