[PATCH] btf_encoder: group all function ELF syms by function name

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



btf_encoder collects function ELF symbols into a table, which is later
used for processing DWARF data and determining whether a function can
be added to BTF.

So far, the ELF symbol name was used as a key for search in this
table, and a search by prefix match was attempted in cases when ELF
symbol name has a compiler-generated suffix.

This implementation has bugs, causing some functions to be
inappropriately excluded from (or included into) BTF.

Rework the implementation of the ELF functions table: use a proper
name of a function (without any suffix) as a key, and collect each
relevant ELF symbol information for later examination. This way we can
guarantee that btf_encoder__find_function() always returns correct
elf_function object (or NULL).

Collect an array of symbol name + address pairs from GElf_Sym for each
elf_function. Then, in saved_functions_combine() use this information
when deciding whether a function should be included in BTF.

In particular, exclude functions with ambiguous address, taking into
account that .cold symbols can be ignored.

Signed-off-by: Ihor Solodrai <ihor.solodrai@xxxxxxxxx>
---
 btf_encoder.c | 248 +++++++++++++++++++++++++++++++++-----------------
 1 file changed, 162 insertions(+), 86 deletions(-)

diff --git a/btf_encoder.c b/btf_encoder.c
index 0bc2334..fcc30aa 100644
--- a/btf_encoder.c
+++ b/btf_encoder.c
@@ -87,16 +87,22 @@ struct btf_encoder_func_state {
 	uint8_t optimized_parms:1;
 	uint8_t unexpected_reg:1;
 	uint8_t inconsistent_proto:1;
+	uint8_t ambiguous_addr:1;
 	int ret_type_id;
 	struct btf_encoder_func_parm *parms;
 	struct btf_encoder_func_annot *annots;
 };
 
+struct elf_function_sym {
+	const char *name;
+	uint64_t addr;
+};
+
 struct elf_function {
-	const char	*name;
-	char		*alias;
-	size_t		prefixlen;
-	bool		kfunc;
+	char		*name;
+	struct elf_function_sym *syms;
+	uint8_t 	sym_cnt;
+	uint8_t		kfunc:1;
 	uint32_t	kfunc_flags;
 };
 
@@ -161,10 +167,18 @@ struct btf_kfunc_set_range {
 	uint64_t end;
 };
 
+static inline void elf_function__free_content(struct elf_function *func) {
+	free(func->name);
+	if (func->sym_cnt)
+		free(func->syms);
+	memset(func, 0, sizeof(*func));
+}
+
 static inline void elf_functions__delete(struct elf_functions *funcs)
 {
-	for (int i = 0; i < funcs->cnt; i++)
-		free(funcs->entries[i].alias);
+	for (int i = 0; i < funcs->cnt; i++) {
+		elf_function__free_content(&funcs->entries[i]);
+	}
 	free(funcs->entries);
 	elf_symtab__delete(funcs->symtab);
 	list_del(&funcs->node);
@@ -981,8 +995,7 @@ static void btf_encoder__log_func_skip(struct btf_encoder *encoder, struct elf_f
 
 	if (!encoder->verbose)
 		return;
-	printf("%s (%s): skipping BTF encoding of function due to ",
-	       func->alias ?: func->name, func->name);
+	printf("%s: skipping BTF encoding of function due to ", func->name);
 	va_start(ap, fmt);
 	vprintf(fmt, ap);
 	va_end(ap);
@@ -1176,6 +1189,33 @@ static struct btf_encoder_func_state *btf_encoder__alloc_func_state(struct btf_e
 	return state;
 }
 
+static inline bool str_has_suffix(const char *str, const char *suffix) {
+	int prefixlen = strlen(str) - strlen(suffix);
+
+	if (prefixlen < 0)
+		return false;
+
+	return !strcmp(str + prefixlen, suffix);
+}
+
+static bool elf_function__has_ambiguous_address(struct elf_function *func) {
+	if (func->sym_cnt <= 1)
+		return false;
+
+	uint64_t addr = 0;
+	for (int i = 0; i < func->sym_cnt; i++) {
+		struct elf_function_sym *sym = &func->syms[i];
+		if (!str_has_suffix(sym->name, ".cold")) {
+			if (addr && addr != sym->addr)
+				return true;
+			else
+			 	addr = sym->addr;
+		}
+	}
+
+	return false;
+}
+
 static int32_t btf_encoder__save_func(struct btf_encoder *encoder, struct function *fn, struct elf_function *func)
 {
 	struct btf_encoder_func_state *state = btf_encoder__alloc_func_state(encoder);
@@ -1191,6 +1231,9 @@ static int32_t btf_encoder__save_func(struct btf_encoder *encoder, struct functi
 
 	state->encoder = encoder;
 	state->elf = func;
+
+	state->ambiguous_addr = elf_function__has_ambiguous_address(func);
+
 	state->nr_parms = ftype->nr_parms + (ftype->unspec_parms ? 1 : 0);
 	state->ret_type_id = ftype->tag.type == 0 ? 0 : encoder->type_id_off + ftype->tag.type;
 	if (state->nr_parms > 0) {
@@ -1294,7 +1337,7 @@ static int32_t btf_encoder__add_func(struct btf_encoder *encoder,
 	int err;
 
 	btf_fnproto_id = btf_encoder__add_func_proto(encoder, NULL, state);
-	name = func->alias ?: func->name;
+	name = func->name;
 	if (btf_fnproto_id >= 0)
 		btf_fn_id = btf_encoder__add_ref_type(encoder, BTF_KIND_FUNC, btf_fnproto_id,
 						      name, false);
@@ -1338,48 +1381,39 @@ static int32_t btf_encoder__add_func(struct btf_encoder *encoder,
 	return 0;
 }
 
-static int functions_cmp(const void *_a, const void *_b)
+static int elf_function__name_cmp(const void *_a, const void *_b)
 {
 	const struct elf_function *a = _a;
 	const struct elf_function *b = _b;
 
-	/* if search key allows prefix match, verify target has matching
-	 * prefix len and prefix matches.
-	 */
-	if (a->prefixlen && a->prefixlen == b->prefixlen)
-		return strncmp(a->name, b->name, b->prefixlen);
 	return strcmp(a->name, b->name);
 }
 
-#ifndef max
-#define max(x, y) ((x) < (y) ? (y) : (x))
-#endif
-
 static int saved_functions_cmp(const void *_a, const void *_b)
 {
 	const struct btf_encoder_func_state *a = _a;
 	const struct btf_encoder_func_state *b = _b;
 
-	return functions_cmp(a->elf, b->elf);
+	return elf_function__name_cmp(a->elf, b->elf);
 }
 
 static int saved_functions_combine(struct btf_encoder_func_state *a, struct btf_encoder_func_state *b)
 {
-	uint8_t optimized, unexpected, inconsistent;
-	int ret;
+	uint8_t optimized, unexpected, inconsistent, ambiguous_addr;
+
+	if (a->elf != b->elf)
+		return 1;
 
-	ret = strncmp(a->elf->name, b->elf->name,
-		      max(a->elf->prefixlen, b->elf->prefixlen));
-	if (ret != 0)
-		return ret;
 	optimized = a->optimized_parms | b->optimized_parms;
 	unexpected = a->unexpected_reg | b->unexpected_reg;
 	inconsistent = a->inconsistent_proto | b->inconsistent_proto;
-	if (!unexpected && !inconsistent && !funcs__match(a, b))
+	ambiguous_addr = a->ambiguous_addr | b->ambiguous_addr;
+	if (!unexpected && !inconsistent && !ambiguous_addr && !funcs__match(a, b))
 		inconsistent = 1;
 	a->optimized_parms = b->optimized_parms = optimized;
 	a->unexpected_reg = b->unexpected_reg = unexpected;
 	a->inconsistent_proto = b->inconsistent_proto = inconsistent;
+	a->ambiguous_addr = b->ambiguous_addr = ambiguous_addr;
 
 	return 0;
 }
@@ -1447,32 +1481,6 @@ out:
 	return err;
 }
 
-static void elf_functions__collect_function(struct elf_functions *functions, GElf_Sym *sym)
-{
-	struct elf_function *func;
-	const char *name;
-
-	if (elf_sym__type(sym) != STT_FUNC)
-		return;
-
-	name = elf_sym__name(sym, functions->symtab);
-	if (!name)
-		return;
-
-	func = &functions->entries[functions->cnt];
-	func->name = name;
-	if (strchr(name, '.')) {
-		const char *suffix = strchr(name, '.');
-
-		functions->suffix_cnt++;
-		func->prefixlen = suffix - name;
-	} else {
-		func->prefixlen = strlen(name);
-	}
-
-	functions->cnt++;
-}
-
 static struct elf_functions *btf_encoder__elf_functions(struct btf_encoder *encoder)
 {
 	struct elf_functions *funcs = NULL;
@@ -1490,13 +1498,12 @@ static struct elf_functions *btf_encoder__elf_functions(struct btf_encoder *enco
 	return funcs;
 }
 
-static struct elf_function *btf_encoder__find_function(const struct btf_encoder *encoder,
-						       const char *name, size_t prefixlen)
+static struct elf_function *btf_encoder__find_function(const struct btf_encoder *encoder, const char *name)
 {
 	struct elf_functions *funcs = elf_functions__find(encoder->cu->elf, &encoder->elf_functions_list);
-	struct elf_function key = { .name = name, .prefixlen = prefixlen };
+	struct elf_function key = { .name = (char*)name };
 
-	return bsearch(&key, funcs->entries, funcs->cnt, sizeof(key), functions_cmp);
+	return bsearch(&key, funcs->entries, funcs->cnt, sizeof(key), elf_function__name_cmp);
 }
 
 static bool btf_name_char_ok(char c, bool first)
@@ -2060,7 +2067,7 @@ static int btf_encoder__collect_kfuncs(struct btf_encoder *encoder)
 			continue;
 		}
 
-		elf_fn = btf_encoder__find_function(encoder, func, 0);
+		elf_fn = btf_encoder__find_function(encoder, func);
 		if (elf_fn) {
 			elf_fn->kfunc = true;
 			elf_fn->kfunc_flags = pair->flags;
@@ -2135,14 +2142,45 @@ int btf_encoder__encode(struct btf_encoder *encoder, struct conf_load *conf)
 	return err;
 }
 
+static inline int elf_function__push_sym(struct elf_function *func, struct elf_function_sym *sym) {
+	struct elf_function_sym *tmp;
+
+	if (func->sym_cnt)
+		tmp = realloc(func->syms, (func->sym_cnt + 1) * sizeof(func->syms[0]));
+	else
+		tmp = calloc(sizeof(func->syms[0]), 1);
+
+	if (!tmp)
+		return -ENOMEM;
+
+	func->syms = tmp;
+	func->syms[func->sym_cnt] = *sym;
+	func->sym_cnt++;
+
+	return 0;
+}
+
+static void print_func_table(struct elf_functions *functions) {
+	for (int i = 0; i < functions->cnt; i++) {
+		struct elf_function *func = &functions->entries[i];
+		printf("%s -> [", func->name);
+		for (int j = 0; j < func->sym_cnt; j++) {
+			printf("{ %s %lx } ", func->syms[j].name, func->syms[j].addr);
+		}
+		printf("]\n");
+	}
+}
+
 static int elf_functions__collect(struct elf_functions *functions)
 {
 	uint32_t nr_symbols = elf_symtab__nr_symbols(functions->symtab);
-	struct elf_function *tmp;
+	struct elf_function *func, *tmp;
 	Elf32_Word sym_sec_idx;
+	const char *sym_name;
 	uint32_t core_id;
+	struct elf_function_sym func_sym;
 	GElf_Sym sym;
-	int err = 0;
+	int err = 0, i, j;
 
 	/* We know that number of functions is less than number of symbols,
 	 * so we can overallocate temporarily.
@@ -2153,18 +2191,75 @@ static int elf_functions__collect(struct elf_functions *functions)
 		goto out_free;
 	}
 
+	/* First, collect an elf_function for each GElf_Sym
+	 * Where func->name is without a suffix
+	 */
 	functions->cnt = 0;
 	elf_symtab__for_each_symbol_index(functions->symtab, core_id, sym, sym_sec_idx) {
-		elf_functions__collect_function(functions, &sym);
+
+		if (elf_sym__type(&sym) != STT_FUNC)
+			continue;
+
+		sym_name = elf_sym__name(&sym, functions->symtab);
+		if (!sym_name)
+			continue;
+
+		func = &functions->entries[functions->cnt];
+
+		const char *suffix = strchr(sym_name, '.');
+		if (suffix) {
+			functions->suffix_cnt++;
+			func->name = strndup(sym_name, suffix - sym_name);
+		} else {
+			func->name = strdup(sym_name);
+		}
+		if (!func->name) {
+			err = -ENOMEM;
+			goto out_free;
+		}
+
+		func_sym.name = sym_name;
+		func_sym.addr = sym.st_value;
+
+		err = elf_function__push_sym(func, &func_sym);
+		if (err)
+			goto out_free;
+
+		functions->cnt++;
 	}
 
+	/* At this point functions->entries is an unordered array of elf_function
+	 * each having a name (without a suffix) and a single elf_function_sym (maybe with suffix)
+	 * Now let's sort this table by name.
+	 */
 	if (functions->cnt) {
-		qsort(functions->entries, functions->cnt, sizeof(*functions->entries), functions_cmp);
+		qsort(functions->entries, functions->cnt, sizeof(*functions->entries), elf_function__name_cmp);
 	} else {
 		err = 0;
 		goto out_free;
 	}
 
+	/* Finally dedup by name, transforming { name -> syms[1] } entries into { name -> syms[n] } */
+	i = 0;
+	j = 1;
+	for (j = 1; j < functions->cnt; j++) {
+		struct elf_function *a = &functions->entries[i];
+		struct elf_function *b = &functions->entries[j];
+
+		if (!strcmp(a->name, b->name)) {
+			elf_function__push_sym(a, &b->syms[0]);
+			elf_function__free_content(b);
+		} else {
+			i++;
+			if (i != j)
+				functions->entries[i] = functions->entries[j];
+		}
+	}
+
+	functions->cnt = i + 1;
+
+	print_func_table(functions);
+
 	/* Reallocate to the exact size */
 	tmp = realloc(functions->entries, functions->cnt * sizeof(struct elf_function));
 	if (tmp) {
@@ -2661,30 +2756,11 @@ int btf_encoder__encode_cu(struct btf_encoder *encoder, struct cu *cu, struct co
 			if (!name)
 				continue;
 
-			/* prefer exact function name match... */
-			func = btf_encoder__find_function(encoder, name, 0);
-			if (!func && funcs->suffix_cnt &&
-			    conf_load->btf_gen_optimized) {
-				/* falling back to name.isra.0 match if no exact
-				 * match is found; only bother if we found any
-				 * .suffix function names.  The function
-				 * will be saved and added once we ensure
-				 * it does not have optimized-out parameters
-				 * in any cu.
-				 */
-				func = btf_encoder__find_function(encoder, name,
-								  strlen(name));
-				if (func) {
-					if (encoder->verbose)
-						printf("matched function '%s' with '%s'%s\n",
-						       name, func->name,
-						       fn->proto.optimized_parms ?
-						       ", has optimized-out parameters" :
-						       fn->proto.unexpected_reg ? ", has unexpected register use by params" :
-						       "");
-					if (!func->alias)
-						func->alias = strdup(name);
-				}
+			func = btf_encoder__find_function(encoder, name);
+			if (!func) {
+				if (encoder->verbose)
+					printf("could not find function '%s' in the ELF functions table\n", name);
+				continue;
 			}
 		} else {
 			if (!fn->external)
-- 
2.50.1


> 
> Third we need to decide how to deal with cases where the function does
> not correspond to an mcount boundary. It'd be interesting to see if the
> filtering helps here, but part of the problem is also that we don't
> currently have a mechanism to help guide the match between function name
> and function site that is done when the fentry attach is carried out.
> Yonghong and I talked about it in [1].
> 
> Addresses seem like the natural means to help guide that, so a
> DATASEC-like set of addresses would help this matching. I had a WIP
> version of this but it wasn't working fully yet. I'll revive it and see
> if I can get it out as an RFC. Needs to take into account the work being
> done on inlines too [1].
> 
> In terms of the tracer's actual intent, multi-site functions are often
> "static inline" functions in a .h file that don't actually get inlined;
> the user intent would be often to trace all instances, but it seems to
> me we need to provide a means to support both this or to trace a
> specific instance. How the latter is best represented from the tracer
> side I'm not sure; raw addresses would be one way I suppose. Absent an
> explicit request from the tracer I'm not sure what heuristics make most
> sense; currently we just pick the first instance I suspect, and might
> need to continue to do so for backwards compatibility.
> 
> 
>> I am not aware of long term plans for addressing this, though it looks
>> like this was discussed before. I'd appreciate if you share any
>> relevant threads.
>>
> 
> Yonghong and I discussed this a bit in [1], and the inline thread in [2]
> has some more details.

Thank you for sharing. I guess I was wrong when I said I'm not aware
of the plans, because I am a little bit familiar with the work on BTF
representation of inlined funcs.

As far as I understand, partly because of the current limitations of
BTF, the best pahole/btf_encoder can do with optimized functions right
now is determine whether a function was not modified too much across
instances (optimized_parms, unexpected_reg etc.), and if yes then
exclude it from BTF.

Anything smarter requires extending BTF.

> 
> [1]
> https://lpc.events/event/18/contributions/1945/attachments/1508/3179/Kernel%20func%20tracing%20in%20the%20face%20of%20compiler%20optimization.pdf
> [2]
> https://lore.kernel.org/bpf/20250416-btf_inline-v1-0-e4bd2f8adae5@xxxxxxxx/
> 
>> Thanks.
>>
>> [...]





[Index of Archives]     [Linux Samsung SoC]     [Linux Rockchip SoC]     [Linux Actions SoC]     [Linux for Synopsys ARC Processors]     [Linux NFS]     [Linux NILFS]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]


  Powered by Linux