函数体外__thread_local在LDM,否则在主存。
函数体内由bsub的时候是否-b决定,没有-b在主存,有-b在LDM;一般有从核的代码提交的时候都加上-b。
“bsub -b 指定从核栈位于局存”
据说__thread_local可能会比直接定义在函数里面慢,没有验证仅供参考。
函数体外__thread_local在LDM,否则在主存。
函数体内由bsub的时候是否-b决定,没有-b在主存,有-b在LDM;一般有从核的代码提交的时候都加上-b。
“bsub -b 指定从核栈位于局存”
据说__thread_local可能会比直接定义在函数里面慢,没有验证仅供参考。
在运行CESM中遇到了1800进程中有 <5 个进程从核UNKOWN EXPT。需要定位到具体expt现场才能修复bug。
一开始使用在所有spawn插桩的方案,但是由于EXPT的进程号会随着调试代码的链接而变化,无法通过rank判断输出部分线程。那么直接的处理办法就是所有进程都输出到屏幕或者每个进程都输出到独立文件的方案。后来采用了后者,但是大规模I/O性能十分低下,原本一个小时出结果的程序预计需要一周,不是一个可靠的方案;前者也有类似问题。
使用wrap在所有的athread_spawn插桩,记录本次athread_spawn调用时的栈帧情况。为了能把记录的最后一次spawn的栈帧信息正确的输出,需要在出错进程退出前输出获得的栈帧信息。这个出错后的处理通过在__expt_handler插桩实现。
“如何通过wrap来插桩”
这是一个由gcc编译器(链接器)提供的功能。具体操作流程:
通过在编译的时候加入-Wl,-wrap=symbol(Wl的意思是之后的参数传递给链接器),来告诉链接器不去链接名为symbol的函数的时候,链接名为__wrap_symbol的函数。
__wrap_symbol是用户自己定义的函数,可以插入用户自己想运行的插桩代码。__wrap_symbol中通过__real_symbol调用原函数。
更多wrap的资料可以见http://www.bubuko.com/infodetail-1328284.html等网页。
“如何看函数原型”
grep -r "athread_spawn" /usr/sw-mpp/
可以在神威的所有头文件中查找一个函数的定义。
从中可以看到athread_spawn是一个define,实际上__real_athread_spawn才是函数实体(其原因估计是为了通过define在函数名前加入slave_)。即我们需要wrap的函数不是athread_spawn,而是__real_athread_spawn。
“如何获取足够深度的栈帧”
通过阅读几个函数的汇编,可以解析神威栈帧的一些基本信息。sp是栈帧的指针,指向栈底的位置。对于当前函数,0(sp)存的就是我们需要的return address。
但是由于神威fortran对于函数特殊的处理方案(据我所知x86的交叉编译方案不同),对于athread_spawn这种函数,fortran会先调用athread_spawn_作为外壳,由外壳调用原来的__real_athread_spawn。这样就需要至少寻找到backtrace(2)。
此外,由于CESM代码还在fortran中使用open ACC,open ACC的调用栈和直接athread库不一样。具体来说,是acc_parallel_start_(fortran外壳)调用了ACC_parallel_start(c的ACC开始函数),再调用了__real_athread_spawn这个c的athread库入口。一共需要寻找到backtrace(3)。
在技术上,我们通过0(sp)可以获得当前函数的返回地址,对于更深的栈帧,则需要一些技巧。和x86架构不同,不少RISC架构如MIPS、alpha没有bp(当前函数的栈顶)指针,无法通过寄存器(即bp-sp)得到栈的深度。但是由于这种任务下我们需要前跳的栈空间大小比较固定,可以通过阅读汇编直接得到栈深度,并hard-coding到代码的方法解决。具体来说:
call __real_athread_spawn Address: asm volatile("ldl %0, 0($sp)":"=r"(stack_frame));
call ACC_parallel_start Address: asm volatile("ldl %0, 32($sp)":"=r"(stack_frame));
call acc_parallel_start_ Address: asm volatile("ldl %0, 96($sp)":"=r"(stack_frame));
其中32是__real_athread_spawn的栈深度,64是ACC_parallel_start的栈深度。调用acc_start的地址即位于产生问题的CESM函数。
(实际上,可以通过分析指令流,寻找ldi sp,*(sp)这种指令来获得栈长度,但是由于有动态分配栈内存的部分,该方法并不具有通用性。同时动态变化长度的指令依赖比较复杂,解析有一定的困难)
“如何捕获从核exception”
我们找了很久,没有现成的资料可以在程序退出前插桩。通过readelf -s a.out | grep -E "exception|expt"可以发现一些类似的函数。通过小case的实验我们最终定位到了__expt_handler这个函数,可以在UNKOWN EXPT前捕获到这个事件。并在其中把栈信息按进程号输出到对应文件,就可以获得需要的调试结果。
此外,为了wrap __expt_handler这个函数,需要获得__expt_handler的参数列表。我们不能通过在头文件寻找其定义,因为__expt_handler不存在于任何头文件中。一个可行的方案是从1个参数试起,逐个往上试,直到试到链接器通过为止。