我对 asm 编程很陌生。我一直在尝试解决一些任务作为学习的方式。其中两个任务运行良好,但两个任务因分段错误而失败。我读了很多文章,但我无法理解它。
这是我的C驱动程序
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
extern int is_palindrome(char *s);
extern void palindrome_check();
int is_palindrome(char *s){
int len=strlen(s);
for(int i=0;i<len/2;i++){
if(s[i] != s[len -i -1]){
return 0;
}
}
return 1;
}
int main() {
int choice;
int result1;
char str2[1024];
do {
printf("\nMenu:\n");
printf("1) Test if a string is a palindrome (from C to ASM)\n");
printf("2) Test if a string is a palindrome (from ASM to C)\n");
printf("Enter your choice: ");
scanf("%d", &choice);
switch (choice) {
case 1:
printf("Enter a string: ");
scanf("%s",str2);
result1=is_palindrome(str2);
if(result1==1){
printf("It is a palindrome");
}
else {
printf("It is NOT a palindrome");}
break;
case 2:
palindrome_check();
break;
}
}
return 0;
}
这是我的functions.asm
BITS 32
extern is_palindrome
GLOBAL is_palindrome
GLOBAL palindrome_check
SECTION .bss
buf1 resb 1024
buf resb 1024
SECTION .data
prompt db "Please enter a string:",0x0A
lenprompt equ $ - prompt
palindrome_msg db "It is a palindrome",0x0A
lenpamsg equ $ - palindrome_msg
not_palindrome_msg db "It is NOT a palindrome",0x0A
lenntpamsg equ $ - not_palindrome_msg
SECTION .text
palindrome_check:
mov eax,4
mov ebx,1
mov ecx,prompt
mov edx,lenprompt
int 0x80
mov eax,3
mov ebx,0
mov ecx,buf
mov edx,1024
int 0x80
push ecx
call is_palindrome_c
add esp,4
cmp eax,1
jne .palindrome_failed
mov eax,4
mov ebx,1
mov ecx,palindrome_msg
mov edx,lenpamsg
int 0x80
.palindrome_failed:
mov eax,4
mov ebx,1
mov ecx,not_palindrome_msg
mov edx,lenntpamsg
int 0x80
is_palindrome:
mov eax,[esp+4]
mov [buf1],eax
;Calculate the length of the string
xor eax,eax
xor edi,edi
mov esi,buf1
len_loop:
inc eax
inc esi
cmp BYTE[esi],10
je set_len
jmp len_loop
set_len:
dec eax
mov [len1],eax
;For the palindrome check function
mov ecx,eax
mov ebx,buf1
call is_palindrome_asm
is_palindrome_asm:
mov eax,[len1]
mov edi,eax
xor ecx,ecx
mov esi,ebx
palindrome_loop:
cmp ecx,edi
jae .done
mov al,[esi+ecx]
mov bl,[esi+edi]
cmp al,bl
jne .not_palindrome
inc ecx
dec edi
jmp palindrome_loop
.not_palindrome:
mov eax,0
ret
.done:
mov eax,1
ret
为了组装文件,我使用了以下命令:
nasm -g -f elf32 -F dwarf -o functions.o functions.asm
gcc -g -Wall -static -m32 -o backandforth backandforth.c functions.o
我已根据所提供的更正编辑了代码。任何方向都会有所帮助。请注意,我说我是绿色的,我安装了 GEF 调试器,但我无法弄清楚错误是什么。 palindrome_check 不再因分段错误错误而退出,但我传递的每个字符串都不是回文。我已经测试了c函数is_palindome_c并确认它工作正常。
您熟悉“范围界定”这个词吗?因为你的问题很可能就在那里。
让我们先从一些 C 代码开始:
void codeA(void)
{
int x = 5;
codeB();
printf("%d\n", x);
}
void codeB(void)
{
int x = 6;
printf("%d\n", x);
}
如果您要调用 codeA,您希望输出是什么样的? codeB 首先打印,所以它将打印 6。但是由于它将
x
设置为 6,这是否意味着 codeA 也会打印 6?
不,当然不是。 C “范围”每个例程都有自己的 x 副本,因此 codeB 的 x 不会覆盖 codeA 的副本。
但是,汇编程序中的情况有些不同。寄存器没有范围。如果将 ebx 寄存器设置为 5,然后调用将其设置为 6 的例程,则返回时它仍然是 6。
可怕吧?如果每次调用子例程时,它都有可能更改所有寄存器的值,那确实会造成混乱。为了安全起见,您似乎必须在调用函数之前保存所有寄存器,然后在返回时将它们全部放回去。
虽然这可行,但它会真的减慢你的代码速度。如果您调用的函数实际上没有覆盖您的任何寄存器,那将是巨大且毫无意义的时间浪费。
因此,做出这些决定的人决定在调用函数时遵循一些约定(恰当地称为“调用约定”)。他们会定义一些寄存器,被调用的例程可以随意更改。如果被调用者更改任何其他寄存器,则必须在返回之前放回原始值。
有许多不同的调用约定。鉴于您正在编写 32 位汇编代码并从 C 调用它,C 代码的编译器将假定您正在使用
cdecl
约定。我链接到了上面的描述,但是here又是这样。该链接的关键点是:
寄存器 EAX、ECX 和 EDX 由调用者保存,其余寄存器由被调用者保存。
所以当
main
调用palindrome_check
时,那么main是调用者,palindrome_check是被调用者。那么当 palindrome_check 这样做时会发生什么:
mov eax,4
mov ebx,1
mov ecx,prompt
mov edx,lenprompt
int 0x80
调用者已保存了 eax、ecx 和 edx,因此您可以随意更改它们。但是 ebx 呢?您(被调用者)正在更改它,这意味着当您返回时,调用者将不会获得其期望的 ebx 值。你违反了规则,结果现在发生了不好的事情。
那么,你能做什么呢?毕竟,您需要使用 ebx 来打印字符串。这就是 int 0x80 的工作原理。因此,您需要做的是保存旧值,通常在被调用例程的顶部,使用如下内容:
push ebx
然后在例程的底部,就在返回之前,您可以使用以下命令恢复原始值:
pop ebx
查看您的代码,您还可以修改其他“被调用者保存”寄存器(例如 edi)。他们也需要被拯救。
这里面有一个技巧,所以我可以让你不必回来问下一个明显的问题。
如果您推送多个寄存器:
push ebx
push edi
然后当你
pop
他们时,你需要以相反的顺序进行:
pop edi
pop ebx
修改“被调用者保存”寄存器(可能)是导致代码崩溃的原因。
您还有另一个问题(我上面提到过)。再看一下codeA,它完成printf后会发生什么?它到达例行程序的末尾并返回,对吧?
但是汇编并不能做到这一点。不存在“例程结束”,并且缩进代码根本没有任何效果。因此,在打印完第一条消息后,它就会继续执行下一条指令。
可能您需要的是在例行程序结束时使用
jmp
,其中包含 pop
说明,然后是 ret
。