为了学习汇编,我正在查看GCC使用-S命令为一些简单的c程序生成的汇编.我有一个add函数,它接受一些int和一些char并将它们加在一起.我只是想知道为什么char参数被压缩到8个字节(pushq)?为什么不只推一个字节?
.file "test.c"
.text
.globl add
.type add, @function
add:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl %edi, -4(%rbp)
movl %esi, -8(%rbp)
movl %edx, -12(%rbp)
movl %ecx, -16(%rbp)
movl %r8d, -20(%rbp)
movl %r9d, -24(%rbp)
movl 16(%rbp), %ecx
movl 24(%rbp), %edx
movl 32(%rbp), %eax
movb %cl, -28(%rbp)
movb %dl, -32(%rbp)
movb %al, -36(%rbp)
movl -4(%rbp), %edx
movl -8(%rbp), %eax
addl %eax, %edx
movl -12(%rbp), %eax
addl %eax, %edx
movl -16(%rbp), %eax
addl %eax, …Run Code Online (Sandbox Code Playgroud) 我读过的,因为它是为"业绩原因"做不同的地方,但我仍然不知道什么是在性能得到这个16字节对齐提高了特殊情况.或者,无论如何,选择这个的原因是什么.
编辑:我想我以误导的方式写了这个问题.我没有询问为什么处理器使用16字节对齐的内存更快地处理事情,这在文档中随处可见.我想要知道的是,强制执行16字节对齐比仅让程序员在需要时自己对齐堆栈更好.我问这个是因为根据我的汇编经验,堆栈实施有两个问题:只有少于1%的执行代码才有用(所以其他99%实际上是开销); 它也是一个非常常见的错误来源.所以我想知道它最终是如何得到回报的.虽然我对此仍有疑问,但我接受了彼得的回答,因为它包含了我原来问题的最详细答案.
这是在参考CVE-2018-8897(其似乎与CVE-2018至1087年如下)中,所描述:
英特尔64和IA-32架构软件开发人员手册(SDM)的系统编程指南中的一些声明在部分或全部操作系统内核的开发中处理不当,导致MOV SS延迟的#DB异常出现意外行为或者POP SS,正如(例如)Windows,macOS,某些Xen配置或FreeBSD中的权限提升或Linux内核崩溃所证明的那样.MOV到SS和POP SS指令禁止中断(包括NMI),数据断点和单步陷阱异常,直到指令边界跟随下一条指令(SDM第3A卷;第6.8.3节).(禁止的数据断点是MOV到SS或POP到SS指令本身访问的存储器.)请注意,中断使能(EFLAGS.IF)系统标志不会禁止调试异常(SDM第3A卷;第2.3节) .如果MOV到SS或POP到SS指令之后的指令是SYSCALL,SYSENTER,INT 3等指令,它们在CPL <3时将控制权转移到操作系统,则在转移到CPL <3之后交付调试例外完成了.操作系统内核可能不期望这种事件顺序,因此可能会在发生事件时遇到意外行为.
在阅读与Linux内核相关的git提交时,我注意到提交消息指出:
x86/entry/64:不要将#ST条目用于#BP堆栈
#BP/int3没什么值得的.我们不允许kprobes在内核中少数几个在CPL0上运行且堆栈无效的地方,并且32位内核永远使用#BP的正常中断门.
此外,在内核模式下,我们不允许在具有usergs的地方使用kprobes,因此"偏执"也是不必要的.
鉴于漏洞,我试图理解提交消息中的最后一句/段落.据我所知,IST条目是指中断堆栈表中可用于处理中断的(据称)"已知良好"堆栈指针之一.我也理解#BP是指一个断点异常(相当于INT3),并且kprobes是声称只在内核中的一些地方以ring 0(CPL0)特权级别运行的调试机制.
但是我在下一部分完全迷失了,这可能是因为"usergs"是一个错字而我只是错过了预期的内容:
此外,在内核模式下,我们不允许在具有usergs的地方使用kprobes,因此"偏执"也是不必要的.
这句话是什么意思?
我经常忘记系统调用中每个参数需要使用的寄存器,每次我忘记时我都会访问这个问题。
x86_64 用户空间函数调用的整数/指针参数的正确顺序是:
%rdi、%rsi、%rdx、%rcx和%r8。%r9(可变参数函数采用 AL = FP 参数的数量,最多 8)
或者对于系统调用,%rax(系统调用调用号)和相同的参数,除了%r10代替%rcx.
记住这些寄存器而不是每次都用谷歌搜索这个问题的最佳方法是什么?
(Microsoft)x64调用约定规定:
参数在寄存器RCX,RDX,R8和R9中传递.如果参数是float/double,则它们在XMM0L,XMM1L,XMM2L和XMM3L中传递.
这很好,但为什么只是漂浮/双打?为什么不通过XMM寄存器传递整数(也可能是指针)?
似乎有点浪费可用空间,不是吗?
我正在尝试使用sys_brksyscall 在Linux中分配一些内存。这是我尝试过的:
BYTES_TO_ALLOCATE equ 0x08
section .text
global _start
_start:
mov rax, 12
mov rdi, BYTES_TO_ALLOCATE
syscall
mov rax, 60
syscall
Run Code Online (Sandbox Code Playgroud)
事情是按照linux调用约定,我希望返回值在rax寄存器中(指向已分配内存的指针)。我在gdb中运行了此文件,并在进行了sys_brksyscall 后注意到以下寄存器内容
在系统调用之前
rax 0xc 12
rbx 0x0 0
rcx 0x0 0
rdx 0x0 0
rsi 0x0 0
rdi 0x8 8
Run Code Online (Sandbox Code Playgroud)
系统调用后
rax 0x401000 4198400
rbx 0x0 0
rcx 0x40008c 4194444 ; <---- What does this value mean?
rdx 0x0 0
rsi 0x0 0
rdi 0x8 8
Run Code Online (Sandbox Code Playgroud)
rcx在这种情况下,我不太了解寄存器中的值。哪个指针可以用作我分配给它的8个字节的开头的指针sys_brk?
我已经学习了关于CPU/ASM/C的基础知识,并且不明白为什么我们需要为不同的OS目标不同地编译C代码.编译器所做的是创建汇编程序代码,然后汇编到二进制机器代码.ASM代码当然因CPU架构(例如ARM)而不同,因为指令集架构不同.
但是当Linux和Windows在同一个CPU上运行时,MOVE/ADD/......之类的机器操作应该是相同的.虽然我知道有一些特定于操作系统的功能,比如打印到终端,但是这个功能可以由stdio.h的不同实现提供.而且,我可以创建一个非常基本的程序,只计算a + b而不打印任何东西,这样我就不需要任何特定于操作系统的代码.为什么我仍然需要为Linux和Windows编译而不是仅仅为我的Linux可执行文件添加.exe-Extension?
在 C 级别,建议通常通过指针传递大于字大小(x86-64 上为 8 字节)的任何内容,并通过值传递任何小于字大小的内容(意味着通过寄存器)。争论的焦点应该是传递 1 个指针值比传递 N 个成员更有效。但这似乎即使对于 N=2 也应该是正确的。那么为什么 ABI 只有在超过 16 个字节时才开始使用内存呢?为什么不是8?
按值传递 16 字节结构与 24 字节传递的代码生成差异示例: https: //godbolt.org/z/5WdjEEr4E
我正在编写一个 JIT 编译器,我惊讶地发现在 Win64 调用约定中,如此多的 x86-64 寄存器是非易失性的(被调用者保留的)。在我看来,非易失性寄存器只是在所有可以使用这些寄存器的函数中做更多的工作。在数值计算的情况下尤其如此,您希望在叶函数中使用许多寄存器,例如某种高度优化的矩阵乘法。但是,例如,16 个 SSE 寄存器中只有 6 个是易失性的,因此如果您需要使用更多,您将有很多溢出要做。
所以是的,我不明白。这里有什么权衡?
一般来说,按照每个寄存器的用途编码x86汇编是否必要或更容易?
x86架构中的寄存器每个都是首先设计用于特殊目的,但现代编译器似乎并不关心它们的使用(除非在某些特殊条件下,例如REP MOV或MUL).
那么,取决于每个寄存器的用途,代码是更容易还是更优化?(不管与某些寄存器相同的特殊指令(或编码))
例如(我可以改用REP MOVSB或LODSB STOSB,但只是为了演示):
第一个代码:
LEA ESI,[AddressOfSomething]
LEA EDI,[AddressOfSomethingElse]
MOV ECX,NUMBER_OF_LOOP
LoopHere:
MOV AL,[ESI]
ADD AL,8
MOV [EDI],AL
ADD ESI,1
ADD EDI,1
CMP AL,0
JNZ LoopHere
TheEnd:
;...
Run Code Online (Sandbox Code Playgroud)
第二代码:
LEA ECX,[AddressOfSomething]
LEA EDX,[AddressOfSomethingElse]
MOV EBX,NUMBER_OF_LOOP
LoopHere:
MOV AL,[ECX]
ADD AL,8
MOV [EDX],AL
ADD ECX,1
ADD EDX,1
CMP AL,0
JNZ LoopHere
TheEnd:
;...
Run Code Online (Sandbox Code Playgroud)
我使用的编译器 - Visual Studio 2015在执行这样的任务时通常使用第二种方法,它不使用寄存器取决于它的用途,相反,编译器只根据其"volatile"选择使用哪个寄存器或"非易失性"特征(在调用函数后).因此,所有高级编程语言编程软件反汇编都使用第二种方法.
另一个有趣的事实是,在ARM语言中,GPR都具有相同的用途,并且被命名为R0-R7,这意味着当代码使用它时,代码将更类似于第二代码.
总而言之,我的观点是这两个代码使用相同的指令,因此无论我使用哪个寄存器,它都应该具有相同的速度.但我是对的吗?哪个代码更容易编码?
assembly ×8
x86-64 ×6
abi ×3
c ×3
linux ×3
x86 ×2
c++ ×1
compilation ×1
cpu ×1
gcc ×1
linux-kernel ×1
optimization ×1
security ×1
sse ×1
system-calls ×1
windows ×1