x86-64 分段错误保存堆栈指针

use*_*143 3 c x86 assembly cygwin x86-64

我目前正在学习本教程,但我不是该学校的学生。

GDB 给了我一个分段错误thread_start

movq  %rsp, (%rdi)   # save sp in old thread's tcb
Run Code Online (Sandbox Code Playgroud)

当我回溯时,这是附加信息:

#0  thread_start () at thread_start.s:16
#1  0x0000000180219e83 in _cygtls::remove(unsigned int)::__PRETTY_FUNCTION__
    () from /usr/bin/cygwin1.dll
#2  0x00000000ffffcc6b in ?? ()
Backtrace stopped: previous frame inner to this frame (corrupt stack?)
Run Code Online (Sandbox Code Playgroud)

作为一个新手,我一生都无法弄清楚为什么。这是我的主文件:

#define STACK_SIZE 1024*1024

//Thread TCB
struct thread {
    unsigned char * stack_pointer;
    void(*initial_function)(void *);
    void * initial_argument;
};

struct thread * current_thread;
struct thread * inactive_thread;

void thread_switch(struct thread * old_t, struct thread * new_t);
void thread_start(struct thread * old_t, struct thread * new_t);

void yield() {
    //swap threads
    struct thread * temp = current_thread;
    current_thread = inactive_thread;
    inactive_thread = temp;

    thread_switch(inactive_thread, current_thread);
}

void thread_wrap() {
   // call the thread's function
    current_thread->initial_function(current_thread->initial_argument);
    yield();
}

int factorial(int n) {
    return n == 0 ? 1 : n * factorial(n - 1);
}

// calls and print the factorial
void fun_with_threads(void * arg) {
    int n = *(int*)arg;
    printf("%d! = %d\n", n, factorial(n));
}
int main() {
    //allocate memory for threads
    inactive_thread = (struct thread*) malloc(sizeof(struct thread));
    current_thread = (struct thread*) malloc(sizeof(struct thread));

    // argument for factorial
    int *p= (int *) malloc(sizeof(int));
    *p = 5;

    // intialise thread
    current_thread->initial_argument =  p; 
    current_thread->initial_function = fun_with_threads;
    current_thread->stack_pointer = ((unsigned char*) malloc(STACK_SIZE)) + STACK_SIZE; 
    thread_start(inactive_thread, current_thread);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

这是我的 thread_start 的 asm 代码

# Inline comment
/* Block comment */

# void thread_switch(struct thread * old_t, struct thread * new_t);

.globl thread_start

thread_start:
  pushq %rbx           # callee-save
  pushq %rbp           # callee-save
  pushq %r12           # callee-save
  pushq %r13           # callee-save
  pushq %r14           # callee-save
  pushq %r15           # callee-save

  movq  %rsp, (%rdi)   # save sp in old thread's tcb
  movq (%rsi), %rsp    # load sp from  new thread

  jmp thread_wrap
Run Code Online (Sandbox Code Playgroud)

和线程开关:

# Inline comment
/* Block comment */

# void thread_switch(struct thread * old_t, struct thread * new_t);

.globl thread_switch

thread_switch:
  pushq %rbx           # callee-save
  pushq %rbp           # callee-save
  pushq %r12           # callee-save
  pushq %r13           # callee-save
  pushq %r14           # callee-save
  pushq %r15           # callee-save
  movq  %rsp, (%rdi)   # save sp in old thread's tcb
  movq (%rsi), %rsp    # load sp from  new thread
  popq  %r15           # callee-restore
  popq  %r14           # callee-restore
  popq  %r13           # callee-restore
  popq  %r12           # callee-restore
  popq  %rbp           # callee-restore
  popq  %rbx           # callee-restore
  ret                  # return
Run Code Online (Sandbox Code Playgroud)

Pet*_*des 5

你在使用 cygwin,对吗?它默认使用 Windows x64 调用约定,而不是 System V x86-64 psABI。所以你的参数不在%rdiand中%rsi

调用约定是Windows x64,但ABI略有不同:long是64位,所以它是LP64而不是LLP64。请参阅cygwin 文档

__attribute__((sysv_abi))您可以使用原型覆盖默认值,但这仅适用于理解 GNU C 的编译器。


Agner Fog 的调用约定指南对如何编写在 Windows 和非 Windows 上汇编为工作函数的源代码提供了一些建议。最直接的就是使用 an#ifdef来选择不同的函数序言。


Intel x64 程序集介绍有点以 Windows 为中心,并详细介绍了 Windows x64__fastcall调用约定。

(后面是示例和内容。这是一个相当大且很好的教程,从非常基本的内容开始,包括如何使用汇编器等工具。我推荐它用于在 Windows 开发环境中学习 x86-64 asm,也许一般来说。)

Windows x64 __fastcall(与 x64 类似__vectorcall,但不在向量寄存器中传递向量)

  • RCX、RDX、R8、R9按从左到右的顺序用于整数和指针参数
  • XMM0、1、2 和 3 用于浮点参数。
  • 其他参数从左到右压入堆栈。
  • 长度小于 64 位的参数不进行零扩展;高位包含垃圾。
  • 调用者有责任在调用函数之前分配 32 字节的“影子空间”(如果需要,用于存储 RCX、RDX、R8 和 R9)。
  • 调用者有责任在调用后清理堆栈。
  • 如果 64 位或更少,则在 RAX 中返回整数返回值(类似于 x86)。
  • 浮点返回值在 XMM0 中返回。
  • 较大的返回值(结构)具有由调用者在堆栈上分配的空间,并且当被调用者被调用时,RCX 包含指向返回空间的指针。然后将整数参数的寄存器使用情况向右推一位。RAX 将此地址返回给调用者。
  • 堆栈是 16 字节对齐的。“call”指令压入一个 8 字节的返回值,因此所有非叶函数在分配堆栈空间时必须将堆栈调整为 16n+8 形式的值。
  • 寄存器 RAX、RCX、RDX、R8、R9、R10 和 R11 被视为易失性寄存器,必须在函数调用时被视为已销毁。RBX、RBP、RDI、RSI、R12、R14、R14 和 R15 必须保存在使用它们的任何函数中。
  • 请注意,浮点(以及 MMX)寄存器没有调用约定。
  • 更多详细信息(可变参数、异常处理、堆栈展开)请访问 Microsoft 的站点。

标签 wiki中的 MS 调用约定文档(以及 System V ABI 文档,以及大量其他好东西)。

另请参阅为什么 Windows64 使用与 x86-64 上的所有其他操作系统不同的调用约定?