osg*_*sgx 12 linux linker gdb glibc avx
用glibc现代x86_64的Linux将检测到CPU具有支持AVX扩展,并会从通用实现许多字符串函数切换到AVX优化版(带ifunc调度员的帮助:1,2).
这个功能可以很好地提高性能,但它可以防止像valgrind(较旧的libVEXs,在valgrind-3.8之前)和gdb的" target record
"(反向执行)之类的工具正常工作(Ubuntu"Z"17.04 beta,gdb 7.12 .50.20170207-0ubuntu2, gcc 6.3.0-8ubuntu1 20170221,Ubuntu GLIBC 2.24-7ubuntu2):
$ cat a.c
#include <string.h>
#define N 1000
int main(){
char src[N], dst[N];
memcpy(dst, src, N);
return 0;
}
$ gcc a.c -o a -fno-builtin
$ gdb -q ./a
Reading symbols from ./a...(no debugging symbols found)...done.
(gdb) start
Temporary breakpoint 1 at 0x724
Starting program: /home/user/src/a
Temporary breakpoint 1, 0x0000555555554724 in main ()
(gdb) record
(gdb) c
Continuing.
Process record does not support instruction 0xc5 at address 0x7ffff7b60d31.
Process record: failed to record execution log.
Program stopped.
__memmove_avx_unaligned_erms () at ../sysdeps/x86_64/multiarch/memmove-vec-unaligned-erms.S:416
416 VMOVU (%rsi), %VEC(4)
(gdb) x/i $pc
=> 0x7ffff7b60d31 <__memmove_avx_unaligned_erms+529>: vmovdqu (%rsi),%ymm4
Run Code Online (Sandbox Code Playgroud)
Process record does not support instruction 0xc5
来自gdb的"目标记录"实现有错误消息" ",因为记录/重放引擎不支持AVX指令(有时在_dl_runtime_resolve_avx
功能上检测到问题):https://sourceware.org/ml/gdb/2016 -08/msg00028.html "一些AVX指令不被过程记录支持",https://bugs.launchpad.net/ubuntu/+source/gdb/+bug/1573786,https://bugs.debian.org/ cgi-bin/bugreport.cgi?bug = 836802,https ://bugzilla.redhat.com/show_bug.cgi?id = 1136403
https://sourceware.org/ml/gdb/2016-08/msg00028.html中提出的解决方案"您可以在运行时重新编译libc(因此ld.so),或者修改__init_cpu_features,从而查看__cpu_features(参见例如strcmp)." 或设置LD_BIND_NOW=1
,但重新编译glibc仍然有AVX,并且ld bind-now没有帮助.
我听说glibc 中有/etc/ld.so.nohwcap
和LD_HWCAP_MASK
配置.它们可以用来禁用ifunc在glibc中调度到AVX优化的字符串函数吗?
怎样的glibc(rtld?)检测AVX,使用cpuid
,用/proc/cpuinfo
(可能不是),或HWCAP AUX(LD_SHOW_AUXV=1 /bin/echo |grep HWCAP
命令给出AT_HWCAP: bfebfbff
)?
看起来在 glibc 的最新版本中实现了一个很好的解决方法:一个“可调”功能,指导优化字符串函数的选择。您可以在此处找到此功能的一般概述以及ifunc-impl-list.c 中glibc 中的相关代码。
这就是我想出来的方法。首先,我拿了gdb投诉的地址:
Process record does not support instruction 0xc5 at address 0x7ffff75c65d4.
然后我在共享库表中查找:
(gdb) info shared
From To Syms Read Shared Object Library
0x00007ffff7fd3090 0x00007ffff7ff3130 Yes /lib64/ld-linux-x86-64.so.2
0x00007ffff76366b0 0x00007ffff766b52e Yes /usr/lib/x86_64-linux-gnu/libubsan.so.1
0x00007ffff746a320 0x00007ffff75d9cab Yes /lib/x86_64-linux-gnu/libc.so.6
...
Run Code Online (Sandbox Code Playgroud)
可以看到这个地址在glibc里面。但具体是什么功能?
(gdb) disassemble 0x7ffff75c65d4
Dump of assembler code for function __strcmp_avx2:
0x00007ffff75c65d0 <+0>: mov %edi,%eax
0x00007ffff75c65d2 <+2>: xor %edx,%edx
=> 0x00007ffff75c65d4 <+4>: vpxor %ymm7,%ymm7,%ymm7
Run Code Online (Sandbox Code Playgroud)
我可以在ifunc-impl-list.c中查找控制选择 avx2 版本的代码:
IFUNC_IMPL (i, name, strcmp,
IFUNC_IMPL_ADD (array, i, strcmp,
HAS_ARCH_FEATURE (AVX2_Usable),
__strcmp_avx2)
IFUNC_IMPL_ADD (array, i, strcmp, HAS_CPU_FEATURE (SSE4_2),
__strcmp_sse42)
IFUNC_IMPL_ADD (array, i, strcmp, HAS_CPU_FEATURE (SSSE3),
__strcmp_ssse3)
IFUNC_IMPL_ADD (array, i, strcmp, 1, __strcmp_sse2_unaligned)
IFUNC_IMPL_ADD (array, i, strcmp, 1, __strcmp_sse2))
Run Code Online (Sandbox Code Playgroud)
看起来AVX2_Usable
是要禁用的功能。让我们相应地重新运行 gdb:
GLIBC_TUNABLES=glibc.cpu.hwcaps=-AVX2_Usable gdb...
在这次迭代中,它抱怨__memmove_avx_unaligned_erms
,这似乎是由启用的AVX_Usable
- 但我在ifunc-memmove.h 中找到了另一条路径由启用AVX_Fast_Unaligned_Load
。回到绘图板:
GLIBC_TUNABLES=glibc.cpu.hwcaps=-AVX2_Usable,-AVX_Fast_Unaligned_Load gdb ...
在最后一轮中,我rdtscp
在 ASAN 共享库中发现了一条指令,因此我在没有地址清理程序的情况下重新编译,最后,它起作用了。
总结:通过一些工作,可以从命令行禁用这些指令并使用 gdb 的记录功能而不会受到严重的攻击。
似乎没有直接的运行时方法来修补特征检测。此检测发生在动态链接器(ld.so)的早期。
目前,对链接程序进行二进制修补似乎是最简单的方法。@osgx 描述了一种覆盖跳转的方法。另一种方法是伪造cpuid结果。通常,在寄存器ebx,ecx和edx 中返回制造商ID的同时,cpuid(eax=0)
返回支持的最高功能。我们在glibc 2.25中有以下代码片段:eax
sysdeps/x86/cpu-features.c
__cpuid (0, cpu_features->max_cpuid, ebx, ecx, edx);
/* This spells out "GenuineIntel". */
if (ebx == 0x756e6547 && ecx == 0x6c65746e && edx == 0x49656e69)
{
/* feature detection for various Intel CPUs */
}
/* another case for AMD */
else
{
kind = arch_kind_other;
get_common_indeces (cpu_features, NULL, NULL, NULL, NULL);
}
Run Code Online (Sandbox Code Playgroud)
该__cpuid
行将转换为/lib/ld-linux-x86-64.so.2
(/lib/ld-2.25.so
)中的这些说明:
172a8: 31 c0 xor eax,eax
172aa: c7 44 24 38 00 00 00 mov DWORD PTR [rsp+0x38],0x0
172b1: 00
172b2: c7 44 24 3c 00 00 00 mov DWORD PTR [rsp+0x3c],0x0
172b9: 00
172ba: 0f a2 cpuid
Run Code Online (Sandbox Code Playgroud)
因此,除了修补分支之外,我们还可以将更cpuid
改为一条nop
指令,该指令将导致最后一个else
分支的调用(因为寄存器将不包含“ GenuineIntel”)。由于最初的eax=0
,cpu_features->max_cpuid
也将为0,if (cpu_features->max_cpuid >= 7)
也将被绕过。
二进制补丁cpuid(eax=0)
通过nop
这个可以用这个工具来完成(x86和x86-64的作品):
172a8: 31 c0 xor eax,eax
172aa: c7 44 24 38 00 00 00 mov DWORD PTR [rsp+0x38],0x0
172b1: 00
172b2: c7 44 24 3c 00 00 00 mov DWORD PTR [rsp+0x3c],0x0
172b9: 00
172ba: 0f a2 cpuid
Run Code Online (Sandbox Code Playgroud)
等效的Perl变体-0777
可确保立即读取文件,而不是在换行符处分开记录:
#!/usr/bin/env python
import re
import sys
infile, outfile = sys.argv[1:]
d = open(infile, 'rb').read()
# Match CPUID(eax=0), "xor eax,eax" followed closely by "cpuid"
o = re.sub(b'(\x31\xc0.{0,32}?)\x0f\xa2', b'\\1\x66\x90', d)
assert d != o
open(outfile, 'wb').write(o)
Run Code Online (Sandbox Code Playgroud)
那是容易的部分。现在,我不想替换系统范围内的动态链接器,而是仅使用此链接器执行一个特定程序。当然,可以使用来完成./ld-linux-x86-64-patched.so.2 ./a
,但是朴素的gdb调用无法设置断点:
$ gdb -q -ex "set exec-wrapper ./ld-linux-x86-64-patched.so.2" -ex start ./a
Reading symbols from ./a...done.
Temporary breakpoint 1 at 0x400502: file a.c, line 5.
Starting program: /tmp/a
During startup program exited normally.
(gdb) quit
$ gdb -q -ex start --args ./ld-linux-x86-64-patched.so.2 ./a
Reading symbols from ./ld-linux-x86-64-patched.so.2...(no debugging symbols found)...done.
Function "main" not defined.
Temporary breakpoint 1 (main) pending.
Starting program: /tmp/ld-linux-x86-64-patched.so.2 ./a
[Inferior 1 (process 27418) exited normally]
(gdb) quit
Run Code Online (Sandbox Code Playgroud)
如何使用自定义elf解释器调试程序中介绍了手动解决方法。它可以工作,但是很遗憾,它是使用的手动操作add-symbol-file
。不过,应该可以使用GDB捕捉点将其自动化。
一点不二进制链接是一种替代方法LD_PRELOAD
荷兰国际集团,它定义了自定义程序库memcpy
,memove
等等。这一操作将优先于glibc的程序。功能的完整列表在中提供sysdeps/x86_64/multiarch/ifunc-impl-list.c
。与glibc 2.25发行版相比,当前HEAD的符号总数更多(grep -Po 'IFUNC_IMPL \(i, name, \K[^,]+' sysdeps/x86_64/multiarch/ifunc-impl-list.c
):
memchr,memcmp,__ memmove_chk,memmove,memrchr,__ memset_chk,memset,rawmemchr,strlen,strnlen,stpncpy,stpcpy,strcasecmp,strcasecmp_l,strcat,strchr,strchpyulul,strpnhr,strcmp,strcmp,strcmpn, strpbrk,strspn,strstr,wcschr,wcsrchr,wcscpy,wcslen,wcsnlen,wmemchr,wmemcmp,wmemset,__ memcpy_chk,memcpy,__ mempcpy_chk,mempcpy,strncmp,__ wmemset_chk,
归档时间: |
|
查看次数: |
2233 次 |
最近记录: |