rep rep是什么意思?
我在Visual Studio 2008上testing了一些代码,并注意到了security_cookie
。 我可以理解它的意义,但我不明白这个指令的目的是什么。
rep ret /* REP to avoid AMD branch prediction penalty */
当然我可以理解评论:)但是这个前缀exaclty和ret
上下文是什么关系?如果ecx
是!= 0会发生什么? 显然ecx
的循环计数在debugging的时候会被忽略,这是可以预料的。
我发现这里的代码是在这里(由编译器注入安全):
void __declspec(naked) __fastcall __security_check_cookie(UINT_PTR cookie) { /* x86 version written in asm to preserve all regs */ __asm { cmp ecx, __security_cookie jne failure rep ret /* REP to avoid AMD branch prediction penalty */ failure: jmp __report_gsfailure } }
有一个博客以这个指令命名。 第一篇文章描述了背后的原因: http : //repzret.org/p/repzret/
基本上,AMD的分支预测器存在一个问题,即当单字节ret
立即跟随引用的代码(和其他一些情况)的条件跳转时,解决方法是添加rep
前缀, CPU,但修复了预测的惩罚。
显然,一些AMD处理器的分支预测器在一个分支的目标或延迟是一条ret
指令时performance不佳,并且添加rep
前缀可以避免这种情况。
关于rep ret
的含义,在Intel指令集参考中没有提到这个指令序列,而rep
的文档并不是很有帮助:
与非string指令一起使用时,REP前缀的行为是未定义的。
这至less意味着rep
不必以重复的方式行事。
现在,从AMD指令集参考 (1.2.6重复前缀):
前缀只能用于这样的string指令。
通常,重复前缀只能用于上面的表1-6,表1-7和表1-8中列出的string指令[不包含ret]。
所以它看起来好像是未定义的行为,但可以假设,在实践中,处理器忽略了ret
指令中的rep
前缀。
正如Trillian的回答所指出的那样, AMD K8和K10在ret
是分支目标时,或者遵循一个条件分支时,在分支预测方面存在问题 。
AMD针对K10(Barcelona)的优化指南build议在这种情况下使用3字节的ret 0
,从堆栈中popup零字节并返回。 这个版本比英特尔的rep ret
差很多。 具有讽刺意味的是,它也比后来的AMD处理器(推土机和之后的版本)更糟糕。所以没有人更改使用基于AMD的Family 10优化指南更新的ret 0
。
处理器手册警告说,未来的处理器可能会不同地解释一个前缀和一个不修改的指令的组合。 理论上这是真的,但是没有人会制造一个不能运行大量现有二进制文件的CPU。
gcc默认使用rep ret
(没有-mtune=intel
,或者-march=haswell
或者其他东西)。 所以大部分的Linux二进制文件都有一个repz ret
。
一旦K10完全过时,gcc可能会在几年内停止使用rep ret
。 再过5年或10年后,几乎所有的二进制文件都将用比这个更新的gcc来构build。 另外15年之后,CPU制造商可能会考虑将f3 c3
字节序列作为(不同的)指令的一部分。
尽pipe如此,仍然会有使用rep ret
传统封闭源二进制文件,没有可用的更新版本,而且有人需要继续运行。 所以无论新特性f3 c3 != rep ret
是否需要禁用(例如,使用BIOS设置),并且实际上该设置都会改变指令 – 解码器的行为,将f3 c3
识别为rep ret
。 如果遗留二进制文件的向后兼容性是不可能的(因为在功耗和晶体pipe方面无法有效地进行),那么IDK将会考虑什么样的时间框架。 远远超过15年,除非这个CPU只是部分市场。
所以使用rep ret
是安全的,因为其他人都已经这样做了。 使用ret 0
是一个坏主意。 在新的代码中,使用rep ret
可能还是一个好主意。 可能还没有太多的AMD PhenomII处理器,但是它们速度不够快,没有额外的返回地址错误预测或问题。
成本是相当小的。 在大多数情况下,它并不会占用额外的空间,因为无论如何它通常会跟随nop
填充。 然而,在导致额外填充的情况下,将会是15B填充到达下一个16B边界的最差情况。 在这种情况下,gcc只能alignment8B。 (与.p2align 4,,10;
要alignment到16B,如果它将采取10个或更less的nop字节,然后.p2align 3
始终alignment到8B。使用gcc -S -o-
产生asm输出到标准输出看看什么时候它这样做。)
因此,如果我们猜测16中的一个rep ret
最终会创build额外的填充,那么ret
将会达到所需的alignment方式,并且额外填充到8B边界,这意味着每个rep
的平均成本为8 * 1 / 16 =半个字节。
rep ret
不常用来加起来很多东西。 例如,firefox和它映射的所有库都只有〜9k的rep ret
实例。 所以这是大约4k字节,在许多文件。 (而且内存less,因为dynamic库中的许多函数都不会被调用。)
# disassemble every shared object mapped by a process. ffproc=/proc/$(pgrep firefox)/ objdump -d "$ffproc/exe" $(sudo ls -l "$ffproc"/map_files/ | awk '/\.so/ {print $NF}' | sort -u) | grep 'repz ret' -c objdump: '(deleted)': No such file # I forgot to restart firefox after the libexpat security update 9649
在Firefox所映射的所有函数库中的所有函数中都计算了rep ret
,而不仅仅是函数。 这有些相关,因为跨function的代码密度越低意味着你的调用分布在更多的内存页面上。 ITLB和L2-TLB只有有限的条目数量。 本地密度对L1I $(和英特尔的uop-cache)很重要。 无论如何, rep ret
影响非常小。
我花了一分钟的时间想到/proc/<pid>/map_files/
不能被进程所有者访问的原因,但是/proc/<pid>/maps
是。 如果一个UID = root进程(例如来自suid-root二进制文件) mmap(2)
是一个0766目录下的0666文件,那么setuid(nobody)
,运行该二进制文件的任何人都可以绕过由于缺lessx for other
目录上的x for other
权限。