Skip to main content
 首页 » 操作系统

Linux gcc中__builtin_return_address学习与使用

2022年07月19日43wuhuacong

一、说明

Built-in函数格式

void * __builtin_return_address(unsigned int level)

此函数返回当前函数或其调用者之一的返回地址。 level 参数是向上扫描调用堆栈的帧数。 值 0 产生当前函数的返回地址,值 1 产生当前函数调用者的返回地址,依此类推。 内联时的预期行为是函数返回函数返回到的地址。 要解决此问题,请使用
noinline 函数属性。

level 参数必须是一个常量整数。

在某些机器上,可能无法确定除当前函数之外的任何函数的返回地址; 在这种情况下,或者当到达栈顶时,该函数返回 0 或随机值。 此外,__builtin_frame_address 可用于确定是否已到达栈顶。

可能需要对返回值进行额外的后处理,请参阅 __builtin_extract_return_addr。

使用非零参数调用此函数可能会产生不可预知的影响,包括使调用程序崩溃。 因此,当 -Wframe-address 选项生效时,会诊断出被认为不安全的调用。 此类调用仅应在调试情况下进行。

二、内核中使用举例

基于linux-5.10

static const char *find_and_get_symobls(unsigned long caller_addr) 
{ 
    struct h_node *cur_node = NULL; 
    struct h_node *new_node = NULL; 
    const char *cur_symbol = NULL; 
    unsigned int cur_key = 0; 
 
    cur_key = (unsigned int) caller_addr & 0x1f; 
    // Try to find symbols from history records 
    hash_for_each_possible(tbl, cur_node, node, cur_key) { 
        if (cur_node->addr == caller_addr) { 
            cur_symbol = cur_node->symbol; 
            break; 
        } 
    } 
    // Symbols not found. Add new records 
    if (!cur_symbol) { 
        new_node = kzalloc(sizeof(struct h_node), GFP_KERNEL); 
        if (!new_node) 
            return NULL; 
        new_node->addr = caller_addr; 
        sprint_symbol(new_node->symbol, caller_addr); 
        cur_symbol = new_node->symbol; 
        hash_add(tbl, &new_node->node, cur_key); 
    } 
    return cur_symbol; 
} 
 
static void my_freq_qos_update_request(void *data, struct freq_qos_request *req, int value) 
{ 
    int cid = 0; 
    const char *caller_info = find_and_get_symobls((unsigned long)__builtin_return_address(1)); 
    if (caller_info) { 
        cid = find_qos_in_cluster(req->qos); 
        trace_freq_qos_user_setting(cid, req->type, value, caller_info); //perf_tracker_trace.h中注册 
    } 
}

打印格式:

mtkPowerMsgHdl-954     [003] ...1   199.220011: freq_qos_user_setting: cid=0 type=2 value=2000000 caller=store_scaling_max_freq+0x5c/0xa0

caller_info 是个 char* 型指针,trace中打印字符串指针的方法如下:

TRACE_EVENT(freq_qos_user_setting, 
    TP_PROTO( 
        int cid, 
        int type, 
        int value, 
        const char *caller 
    ), 
 
    TP_ARGS(cid, type, value, caller), 
 
    TP_STRUCT__entry( 
        __field(int, cid) 
        __field(int, type) 
        __field(int, value) 
        __string(caller, caller) 
    ), 
 
    TP_fast_assign( 
        __entry->cid = cid; 
        __entry->type = type; 
        __entry->value = value; 
        __assign_str(caller, caller); 
    ), 
 
    TP_printk("cid=%d type=%d value=%d caller=%s", 
        __entry->cid, 
        __entry->type, 
        __entry->value, 
        __get_str(caller) 
    ) 
);

三、使用总结

1. 由于编译器优化,优化成inline的情况出现,__builtin_return_address(n)与底n层函数调用没有确切的对应关系了,使用之前需要验证。

qos_debug_work_func_1 //应该算作__builtin_return_address(1) 
    freq_qos_update_request //应该算作__builtin_return_address(0) 
        trace_android_vh_freq_qos_update_request //1 
            mtk_freq_qos_update_request //2 1和2是trace hook注册的函数,应该不计为函数调用 
                caller_info = find_and_get_symobls((unsigned long)__builtin_return_address(1)); //只是做了个优化,等效于直接%pS打印__builtin_return_address(1) 
                trace_freq_qos_user_setting(cid, type, value, caller_info); //caller_info打印的是"qos_debug_work_func_1",而不是freq_qos_update_request

注:find_and_get_symobls() 不单会打印调用函数名,还会打印ko模块名。

2. 使用下面方法可以达到类似的效果,但是只会打印函数名

printk("caller=%pS\n", __builtin_return_address(1)); //会打印 caller=qos_debug_work_func_1+0xc4/0x17c

本文参考链接:https://www.cnblogs.com/hellokitty2/p/15887006.html