采用 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 literalsC 语言中被认为是 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)

如果您将 index1index2 保留为有符号整数 (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

如果您要为 indexindex2 使用 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 位。这对所有通用寄存器都是一样的。


RBXRBPR12–R15是non-volatile寄存器根据x86-64 System V ABI。如果您的函数修改了它们,则必须保留它们的内容。您可以将它们压入堆栈并在完成时将它们的原始值从堆栈中弹出。首选方法是使用不需要保留的易失性寄存器之一,如 R8-R11RAX RCX, RDX, RDI, RSI.


当您使用 64 位寄存器移动数据 to/from 内存时,将传输 64 位(8 字节)。例如:

mov r14,[rdi+rsi]

将内存地址[rdi+rsi]开始的8个字节移动到64位寄存器R14。稍后的写入会做类似的事情,但会更新内存中的 8 个字节而不是一个字节。如果将字符数组放在堆栈上,则更新 8 个字节的数据可能会破坏堆栈,这恰好是您的代码和环境中的情况。

当使用编号寄存器 R8R15 时,您可以通过在 b 后缀上引用低 8 位寄存器名称的结尾(w 用于 16 位字,d 用于 32 位双字)。 64 位模式 NASM/YASM 语法中所有寄存器名称的 complete chart 是:

mov r14,[rdi+rsi] 可以写成 mov mov r14b,[rdi+rsi] 来移动一个字节。您还必须对其他每个动作进行更改。


假设您将 index1index2 更改为类型 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) 进行交换。由于我们不必保留这些寄存器,因此无需保存和恢复它们。