采用 char 数组和 2 个索引的函数;交换这些索引中的字符
Function that takes a char array and 2 indices; swapping the chars in those indices
这是我的函数原型:
char* swap(char* array, int index1, int index2);
这是我的汇编代码:
segment .text
global swap
swap:
mov r14,[rdi+rsi]
mov r15,[rdi+rdx]
mov [rdi+rsi],r15 ;this line segfaults
mov [rdi+rdx],r14
mov rax,rdi
ret
行mov [rdi+rsi],r15
和mov [rdi+rdx],r14
给我一个段错误;我不确定我哪里出错了
调用函数:
#include <stdio.h>
#include <stdlib.h>
extern char* swapLetters(char* str, int indexA, int indexB);
int main()
{
char* st= "E X A M P L E";
printf("Before swap: \t%s\n", st);
char * res = swap(st, 2 ,10);
printf("After swap: \t%s\n", res);
return 0;
}
预期输出:
交换前:E X A M P L E
交换后:E L A M P X E
这里的部分问题是 non-writable 内存区域正在被用来写入,它不会工作。 (asm 还存在其他正确性问题,请参阅@MichaelPetch 的回答。)
创建时间:
char* st= "E X A M P L E";
因为它创建了一个string literal,指针st
指向一个不可写的内存位置。
如果创建为:
char st[] = "E X A M P L E";
st
存储在可写内存中,其内容 是 字符,而不是仅仅保存指向 read-only 字符串文字的指针。
主要问题是您的 st
变量被定义为指向字符串文字的指针。
char* st= "E X A M P L E";
String literals 在 C 语言中被认为是 read-only。修改这样的字符串是未定义的行为。发生什么是未知的,并且将特定于编译器及其运行环境。当您在汇编代码中写入该内存时,您的环境会引发异常。在大多数使用现代编译器的现代操作系统上,字符串文字被放置在不可写的内存中,因此它会产生异常,这就是您的情况。
如果你想在可写内存中创建一个字符数组,你可以这样定义st
:
char st[] = "E X A M P L E";
汇编代码问题
一个问题是函数 swap
的索引是 int
。在 64 位 GCC/CLANG int
中是 32 位。如果将 32 位带符号的 int
传递给汇编代码,则前 32 位可能包含垃圾。鉴于您的索引永远不会为负,您应该使用无符号类型,最好是 64 位类型。我建议改用 size_t
类型。 size_t
在 x86-64 代码中将是无符号和 64 位大小的,因此当传递给汇编代码时,您不需要 sign/zero 在使用它们之前将索引值扩展到 64 位。我建议将 swap
更改为:
char* swap(char* array, size_t index1, size_t index2)
如果您将 index1
和 index2
保留为有符号整数 (int
),您的汇编代码的开头必须在两个 [=82= 上使用 MOVSX ]ESI 和 EDX 寄存器。该代码如下所示:
swap:
movsx rsi, esi ; Sign extend 32-bit index1 parm in ESI to 64-bits
movsx rdx, edx ; Sign extend 32-bit index2 parm in EDX to 64-bits
; rest of function here
如果您要为 index
和 index2
使用 32 位 unsigned int
,则必须使用以下方法对 32 位值进行零扩展:
mov esi, esi ; Zero extend 32-bit index1 parm in ESI to 64-bits
mov edx, edx ; Zero extend 32-bit index2 parm in EDX to 64-bits
; rest of function here
当操作的目标是 64 位模式下的 32 位寄存器时,CPU 自动将目标寄存器的高 32 位清零。将像 ESI 这样的 32 位寄存器移动到自身将清除 RSI 的高 32 位。这对所有通用寄存器都是一样的。
RBX、RBP和R12–R15是non-volatile寄存器根据x86-64 System V ABI。如果您的函数修改了它们,则必须保留它们的内容。您可以将它们压入堆栈并在完成时将它们的原始值从堆栈中弹出。首选方法是使用不需要保留的易失性寄存器之一,如 R8-R11、RAX、 RCX, RDX, RDI, RSI.
当您使用 64 位寄存器移动数据 to/from 内存时,将传输 64 位(8 字节)。例如:
mov r14,[rdi+rsi]
将内存地址[rdi+rsi]
开始的8个字节移动到64位寄存器R14。稍后的写入会做类似的事情,但会更新内存中的 8 个字节而不是一个字节。如果将字符数组放在堆栈上,则更新 8 个字节的数据可能会破坏堆栈,这恰好是您的代码和环境中的情况。
当使用编号寄存器 R8 到 R15 时,您可以通过在 b
后缀上引用低 8 位寄存器名称的结尾(w
用于 16 位字,d
用于 32 位双字)。 64 位模式 NASM/YASM 语法中所有寄存器名称的 complete chart 是:
mov r14,[rdi+rsi]
可以写成 mov mov r14b,[rdi+rsi]
来移动一个字节。您还必须对其他每个动作进行更改。
假设您将 index1
和 index2
更改为类型 size_t
(或 uin64_t
),您的汇编代码可以写成:
segment .text
global swap
swap:
push r14 ; Save non-volatile registers we overwrite
push r15
mov r14b,[rdi+rsi] ; Move one byte from [rdi+rsi] to R14B. R14B is lower 8 bits of R14
mov r15b,[rdi+rdx] ; Move one byte from [rdi+rdx] to R15B. R15B is lower 8 bits of R15
mov [rdi+rsi],r15b ; Move the byte in R15B to [rdi+rsi]
mov [rdi+rdx],r14b ; Move the byte in R14B to [rdi+rdx]
mov rax,rdi
pop r15 ; Restore non-volatile registers
pop r14
ret
如果您要使用其他易失性寄存器而不是 non-volatile 代码可以简化为:
segment .text
global swap
swap:
mov al,[rdi+rsi] ; Move one byte from [rdi+rsi] to AL. AL is lower 8 bits of RAX
mov cl,[rdi+rdx] ; Move one byte from [rdi+rdx] to CL. CL is lower 8 bits of RCX
mov [rdi+rsi],cl ; Move the byte in CL to [rdi+rsi]
mov [rdi+rdx],al ; Move the byte in AL to [rdi+rdx]
mov rax,rdi
ret
在这种情况下,我们使用易失性寄存器的低 8 位 RAX(AL) 和 RCX(CL) 进行交换。由于我们不必保留这些寄存器,因此无需保存和恢复它们。
这是我的函数原型:
char* swap(char* array, int index1, int index2);
这是我的汇编代码:
segment .text
global swap
swap:
mov r14,[rdi+rsi]
mov r15,[rdi+rdx]
mov [rdi+rsi],r15 ;this line segfaults
mov [rdi+rdx],r14
mov rax,rdi
ret
行mov [rdi+rsi],r15
和mov [rdi+rdx],r14
给我一个段错误;我不确定我哪里出错了
调用函数:
#include <stdio.h>
#include <stdlib.h>
extern char* swapLetters(char* str, int indexA, int indexB);
int main()
{
char* st= "E X A M P L E";
printf("Before swap: \t%s\n", st);
char * res = swap(st, 2 ,10);
printf("After swap: \t%s\n", res);
return 0;
}
预期输出:
交换前:E X A M P L E
交换后:E L A M P X E
这里的部分问题是 non-writable 内存区域正在被用来写入,它不会工作。 (asm 还存在其他正确性问题,请参阅@MichaelPetch 的回答。)
创建时间:
char* st= "E X A M P L E";
因为它创建了一个string literal,指针st
指向一个不可写的内存位置。
如果创建为:
char st[] = "E X A M P L E";
st
存储在可写内存中,其内容 是 字符,而不是仅仅保存指向 read-only 字符串文字的指针。
主要问题是您的 st
变量被定义为指向字符串文字的指针。
char* st= "E X A M P L E";
String literals 在 C 语言中被认为是 read-only。修改这样的字符串是未定义的行为。发生什么是未知的,并且将特定于编译器及其运行环境。当您在汇编代码中写入该内存时,您的环境会引发异常。在大多数使用现代编译器的现代操作系统上,字符串文字被放置在不可写的内存中,因此它会产生异常,这就是您的情况。
如果你想在可写内存中创建一个字符数组,你可以这样定义st
:
char st[] = "E X A M P L E";
汇编代码问题
一个问题是函数 swap
的索引是 int
。在 64 位 GCC/CLANG int
中是 32 位。如果将 32 位带符号的 int
传递给汇编代码,则前 32 位可能包含垃圾。鉴于您的索引永远不会为负,您应该使用无符号类型,最好是 64 位类型。我建议改用 size_t
类型。 size_t
在 x86-64 代码中将是无符号和 64 位大小的,因此当传递给汇编代码时,您不需要 sign/zero 在使用它们之前将索引值扩展到 64 位。我建议将 swap
更改为:
char* swap(char* array, size_t index1, size_t index2)
如果您将 index1
和 index2
保留为有符号整数 (int
),您的汇编代码的开头必须在两个 [=82= 上使用 MOVSX ]ESI 和 EDX 寄存器。该代码如下所示:
swap:
movsx rsi, esi ; Sign extend 32-bit index1 parm in ESI to 64-bits
movsx rdx, edx ; Sign extend 32-bit index2 parm in EDX to 64-bits
; rest of function here
如果您要为 index
和 index2
使用 32 位 unsigned int
,则必须使用以下方法对 32 位值进行零扩展:
mov esi, esi ; Zero extend 32-bit index1 parm in ESI to 64-bits
mov edx, edx ; Zero extend 32-bit index2 parm in EDX to 64-bits
; rest of function here
当操作的目标是 64 位模式下的 32 位寄存器时,CPU 自动将目标寄存器的高 32 位清零。将像 ESI 这样的 32 位寄存器移动到自身将清除 RSI 的高 32 位。这对所有通用寄存器都是一样的。
RBX、RBP和R12–R15是non-volatile寄存器根据x86-64 System V ABI。如果您的函数修改了它们,则必须保留它们的内容。您可以将它们压入堆栈并在完成时将它们的原始值从堆栈中弹出。首选方法是使用不需要保留的易失性寄存器之一,如 R8-R11、RAX、 RCX, RDX, RDI, RSI.
当您使用 64 位寄存器移动数据 to/from 内存时,将传输 64 位(8 字节)。例如:
mov r14,[rdi+rsi]
将内存地址[rdi+rsi]
开始的8个字节移动到64位寄存器R14。稍后的写入会做类似的事情,但会更新内存中的 8 个字节而不是一个字节。如果将字符数组放在堆栈上,则更新 8 个字节的数据可能会破坏堆栈,这恰好是您的代码和环境中的情况。
当使用编号寄存器 R8 到 R15 时,您可以通过在 b
后缀上引用低 8 位寄存器名称的结尾(w
用于 16 位字,d
用于 32 位双字)。 64 位模式 NASM/YASM 语法中所有寄存器名称的 complete chart 是:
mov r14,[rdi+rsi]
可以写成 mov mov r14b,[rdi+rsi]
来移动一个字节。您还必须对其他每个动作进行更改。
假设您将 index1
和 index2
更改为类型 size_t
(或 uin64_t
),您的汇编代码可以写成:
segment .text
global swap
swap:
push r14 ; Save non-volatile registers we overwrite
push r15
mov r14b,[rdi+rsi] ; Move one byte from [rdi+rsi] to R14B. R14B is lower 8 bits of R14
mov r15b,[rdi+rdx] ; Move one byte from [rdi+rdx] to R15B. R15B is lower 8 bits of R15
mov [rdi+rsi],r15b ; Move the byte in R15B to [rdi+rsi]
mov [rdi+rdx],r14b ; Move the byte in R14B to [rdi+rdx]
mov rax,rdi
pop r15 ; Restore non-volatile registers
pop r14
ret
如果您要使用其他易失性寄存器而不是 non-volatile 代码可以简化为:
segment .text
global swap
swap:
mov al,[rdi+rsi] ; Move one byte from [rdi+rsi] to AL. AL is lower 8 bits of RAX
mov cl,[rdi+rdx] ; Move one byte from [rdi+rdx] to CL. CL is lower 8 bits of RCX
mov [rdi+rsi],cl ; Move the byte in CL to [rdi+rsi]
mov [rdi+rdx],al ; Move the byte in AL to [rdi+rdx]
mov rax,rdi
ret
在这种情况下,我们使用易失性寄存器的低 8 位 RAX(AL) 和 RCX(CL) 进行交换。由于我们不必保留这些寄存器,因此无需保存和恢复它们。