【问题标题】:x86-64 segmentation fault saving stack pointerx86-64 分段错误保存堆栈指针
【发布时间】:2016-11-29 22:46:30
【问题描述】:

我目前正在关注this tutorial, 但我不是那所学校的学生。

GDB 在thread_start 在线给我一个分段错误:

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

这是我回溯时的附加信息:

#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?)

作为一个新手,我一辈子都想不通为什么。这是我的主要文件:

#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;
}

这是我的 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

和线程开关:

# 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

【问题讨论】:

  • 执行该指令时寄存器rdi 中的值是多少?括号表示您正在取消引用它包含的指针,因此如果指针无效,您的代码将出现分段错误。
  • @CodyGray 的值应该是 thread_start 的第一个参数,它是 inactive_thread,一个指向结构线程的指针?
  • 您的代码对我有用。尝试包含 ?
  • @user2214143:因此请检查调试器是否包含您期望的内容。请参阅x86 tag wiki 的底部以获取有关将 gdb 用于 asm 的提示,或使用您喜欢的任何调试器。

标签: c assembly x86 cygwin x86-64


【解决方案1】:

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

调用约定是 Windows x64,但 ABI 略有不同:long 是 64 位,所以它是 LP64 而不是 LLP64。见the cygwin docs

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


Agner Fog's calling convention guide 有一些关于如何编写源代码以在 Windows 和非 Windows 上组合成工作函数的建议。最直接的就是使用#ifdef来选择不同的函数序言。


这个Intel intro to x64 assembly 有点以 Windows 为中心,详细介绍了 Windows x64 __fastcall 调用约定。

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

Windows x64 __fastcall(类似于 x64 __vectorcall,但不在向量 reg 中传递向量)

  • RCX、RDX、R8、R9 用于整数和指针参数按从左到右的顺序
  • XMM0、1、2 和 3 用于浮点参数。
  • 其他参数从左到右压入堆栈。
  • 长度小于 64 位的参数不进行零扩展;高位包含垃圾。
  • 调用者有责任在调用 功能。
  • 调用者有责任在调用后清理堆栈。
  • 如果 64 位或更少,则在 RAX 中返回整数返回值(类似于 x86)。
  • 在 XMM0 中返回浮点返回值。
  • 较大的返回值(结构)由调用者在堆栈上分配空间,然后 RCX 包含一个指向返回空间的指针,当 被调用者被调用。然后是整数参数的寄存器用法 推了一个到右边。 RAX 将此地址返回给调用者。
  • 堆栈是 16 字节对齐的。 “调用”指令压入一个 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 文档,以及大量其他好东西)。

另见Why does Windows64 use a different calling convention from all other OSes on x86-64?

【讨论】:

  • 作为参考,调用约定在 Cygwin docs 中指定。它实际上使用 Microsoft x64 调用约定。 Cygwin 与 MinGW 和 Windows 的不同之处在于它使用 LP64 数据模型而不是 LLP64。
  • 啊,Cygwin 不错!我看到了,但没有想到,因为我认为它使用的是 System V ABI,而不是 Windows。考虑到他们不遗余力地模仿 *nix 行为,这似乎是一个相当奇怪的选择。我不同意英特尔文档选择将 Windows x64 调用约定称为“fastcall”。 Fastcall 存在于 32 位代码中,但在许多重要方面与 x64 约定有很大不同。他们实际上只分享了在 regs 中传递一些 args 的肤浅设计选择,因此重用名称是令人困惑的。
  • @CodyGray:Cygwin 使用 Windows ABI,因此它可以调用 Windows 库,并制作可以被 Windows 代码调用的库。我感觉合理;它是 POSIX 源代码兼容层,而不是 Linux 二进制兼容层,也不支持 int 0x80syscall。 (即它不是 WINE)。
  • @CodyGray:这很奇怪,但 Microsoft calls it __fastcall 用于 x64。尽管该页面链接到 32 位 __fastcall 文档,其中显示 The __fastcall keyword is accepted and ignored by the compilers that target ARM and x64 architectures; on an x64 chip, by convention, the first four arguments ...。他们肯定会在 32 位版本和 64 位版本中使用 __vectorcall,这有很大的不同。
  • 是的,我知道 vectorcall 在含义上是重载的,但是我没有看到任何使用 fastcall 来引用 x64 调用约定的 Microsoft 文档。那实在是太糟糕了。哦,好吧,我想这并不令人惊讶。糟糕的 fastcall 总是意义超载,以至于对于 32 位构建实际上毫无意义。它从未标准化,每个供应商(MS、Borland、Watcom 等)都实施了自己的看法。 Microsoft 的编译器会忽略所有调用约定说明符,但 x64 构建时会忽略 __vectorcall
猜你喜欢
  • 2020-06-07
  • 2012-12-13
  • 1970-01-01
  • 1970-01-01
  • 2013-12-31
  • 2019-09-30
  • 1970-01-01
  • 2012-09-16
  • 2021-09-01
相关资源
最近更新 更多