我为Project Euler Q14编写了这两个解决方案,在汇编和C++中.它们是用于测试Collatz猜想的相同蛮力方法.装配解决方案与组装
nasm -felf64 p14.asm && gcc p14.o -o p14
Run Code Online (Sandbox Code Playgroud)
C++是用.编译的
g++ p14.cpp -o p14
Run Code Online (Sandbox Code Playgroud)
部件, p14.asm
section .data
fmt db "%d", 10, 0
global main
extern printf
section .text
main:
mov rcx, 1000000
xor rdi, rdi ; max i
xor rsi, rsi ; i
l1:
dec rcx
xor r10, r10 ; count
mov rax, rcx
l2:
test rax, 1
jpe even
mov rbx, 3
mul rbx
inc rax
jmp c1
even:
mov rbx, 2 …
Run Code Online (Sandbox Code Playgroud) 我有一个int x
。为简单起见,假设int
s 占据范围 -2^31 到 2^31-1。我想计算2*x-1
. 我允许x
为任何值 0 <= x
<= 2^30。如果我计算 2*(2^30),我会得到 2^31,这是整数溢出。
一种解决方案是计算2*(x-1)+1
. 比我想要的多了一项减法,但这不应该溢出。但是,编译器会将其优化为2*x-1
. 这是源代码的问题吗?这是可执行文件的问题吗?
这是 Godbolt 的输出2*x-1
:
func(int): # @func(int)
lea eax, [rdi + rdi]
dec eax
ret
Run Code Online (Sandbox Code Playgroud)
这是 Godbolt 的输出2*(x-1)+1
:
func(int): # @func(int)
lea eax, [rdi + rdi]
dec eax
ret
Run Code Online (Sandbox Code Playgroud) c++ integer-overflow compiler-optimization undefined-behavior integer-arithmetic
我被告知并且从英特尔的手册中读到可以将指令写入内存,但是指令预取队列已经获取了陈旧的指令并将执行那些旧的指令.我没有成功观察到这种行为.我的方法如下.
英特尔软件开发手册从第11.6节开始说明
对当前在处理器中高速缓存的代码段中的存储器位置的写入导致相关联的高速缓存行(或多个行)无效.此检查基于指令的物理地址.此外,P6系列和奔腾处理器检查对代码段的写入是否可以修改已经预取执行的指令.如果写入影响预取指令,则预取队列无效.后一种检查基于指令的线性地址.
所以,看起来如果我希望执行陈旧的指令,我需要有两个不同的线性地址引用相同的物理页面.所以,我将内存映射到两个不同的地址.
int fd = open("code_area", O_RDWR | O_CREAT, S_IRWXU | S_IRWXG | S_IRWXO);
assert(fd>=0);
write(fd, zeros, 0x1000);
uint8_t *a1 = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_FILE | MAP_SHARED, fd, 0);
uint8_t *a2 = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_FILE | MAP_SHARED, fd, 0);
assert(a1 != a2);
Run Code Online (Sandbox Code Playgroud)
我有一个汇编函数,它接受一个参数,一个指向我想要更改的指令的指针.
fun:
push %rbp
mov %rsp, %rbp
xorq %rax, %rax # Return value 0
# A far jump simulated with a far return
# Push the …
Run Code Online (Sandbox Code Playgroud) Valgrind选择了一个条件跳转或移动取决于我的一个单元测试中未初始化的值.
检查程序集,我意识到以下代码:
bool operator==(MyType const& left, MyType const& right) {
// ... some code ...
if (left.getA() != right.getA()) { return false; }
// ... some code ...
return true;
}
Run Code Online (Sandbox Code Playgroud)
在哪里MyType::getA() const -> std::optional<std::uint8_t>
,生成以下程序集:
0x00000000004d9588 <+108>: xor eax,eax
0x00000000004d958a <+110>: cmp BYTE PTR [r14+0x1d],0x0
0x00000000004d958f <+115>: je 0x4d9597 <... function... +123>
x 0x00000000004d9591 <+117>: mov r15b,BYTE PTR [r14+0x1c]
x 0x00000000004d9595 <+121>: mov al,0x1
0x00000000004d9597 <+123>: xor edx,edx
0x00000000004d9599 <+125>: cmp BYTE PTR [r13+0x1d],0x0 …
Run Code Online (Sandbox Code Playgroud) 假设您知道您的软件只能运行在两个补码机器上,其中已经很好地定义了带符号的溢出行为.签名溢出仍然是C和C++中未定义的行为,编译器可以用"ret"替换你的整个程序,开始核战争,格式化你的驱动器,或让恶魔飞出你的鼻子.
假设您已在内联asm中签名溢出,您的程序是否仍然调用UB?
如果是,那么单独编译和链接汇编程序呢?
在一次采访中,有人问我是否知道x64指令的行为有所不同,具体取决于使用的CPU,我在任何地方找不到任何文档,有谁知道这些指令是什么以及为什么会这样?
assembly ×4
c++ ×4
x86 ×3
c ×2
x86-64 ×2
c++17 ×1
caching ×1
gcc ×1
optimization ×1
performance ×1