mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git
synced 2025-01-15 13:15:57 +00:00
tools: Add gendwarfksyms
Add a basic DWARF parser, which uses libdw to traverse the debugging information in an object file and looks for functions and variables. In follow-up patches, this will be expanded to produce symbol versions for CONFIG_MODVERSIONS from DWARF. Signed-off-by: Sami Tolvanen <samitolvanen@google.com> Reviewed-by: Petr Pavlu <petr.pavlu@suse.com> Signed-off-by: Masahiro Yamada <masahiroy@kernel.org>
This commit is contained in:
parent
a56fece7f3
commit
f28568841a
@ -9550,6 +9550,13 @@ W: https://linuxtv.org
|
|||||||
T: git git://linuxtv.org/media.git
|
T: git git://linuxtv.org/media.git
|
||||||
F: drivers/media/radio/radio-gemtek*
|
F: drivers/media/radio/radio-gemtek*
|
||||||
|
|
||||||
|
GENDWARFKSYMS
|
||||||
|
M: Sami Tolvanen <samitolvanen@google.com>
|
||||||
|
L: linux-modules@vger.kernel.org
|
||||||
|
L: linux-kbuild@vger.kernel.org
|
||||||
|
S: Maintained
|
||||||
|
F: scripts/gendwarfksyms/
|
||||||
|
|
||||||
GENERIC ARCHITECTURE TOPOLOGY
|
GENERIC ARCHITECTURE TOPOLOGY
|
||||||
M: Sudeep Holla <sudeep.holla@arm.com>
|
M: Sudeep Holla <sudeep.holla@arm.com>
|
||||||
L: linux-kernel@vger.kernel.org
|
L: linux-kernel@vger.kernel.org
|
||||||
|
@ -169,6 +169,14 @@ config MODVERSIONS
|
|||||||
make them incompatible with the kernel you are running. If
|
make them incompatible with the kernel you are running. If
|
||||||
unsure, say N.
|
unsure, say N.
|
||||||
|
|
||||||
|
config GENDWARFKSYMS
|
||||||
|
bool "gendwarfksyms (from debugging information)"
|
||||||
|
depends on DEBUG_INFO
|
||||||
|
# Requires full debugging information, split DWARF not supported.
|
||||||
|
depends on !DEBUG_INFO_REDUCED && !DEBUG_INFO_SPLIT
|
||||||
|
# Requires ELF object files.
|
||||||
|
depends on !LTO
|
||||||
|
|
||||||
config ASM_MODVERSIONS
|
config ASM_MODVERSIONS
|
||||||
bool
|
bool
|
||||||
default HAVE_ASM_MODVERSIONS && MODVERSIONS
|
default HAVE_ASM_MODVERSIONS && MODVERSIONS
|
||||||
|
@ -54,6 +54,7 @@ targets += module.lds
|
|||||||
|
|
||||||
subdir-$(CONFIG_GCC_PLUGINS) += gcc-plugins
|
subdir-$(CONFIG_GCC_PLUGINS) += gcc-plugins
|
||||||
subdir-$(CONFIG_MODVERSIONS) += genksyms
|
subdir-$(CONFIG_MODVERSIONS) += genksyms
|
||||||
|
subdir-$(CONFIG_GENDWARFKSYMS) += gendwarfksyms
|
||||||
subdir-$(CONFIG_SECURITY_SELINUX) += selinux
|
subdir-$(CONFIG_SECURITY_SELINUX) += selinux
|
||||||
subdir-$(CONFIG_SECURITY_IPE) += ipe
|
subdir-$(CONFIG_SECURITY_IPE) += ipe
|
||||||
|
|
||||||
|
2
scripts/gendwarfksyms/.gitignore
vendored
Normal file
2
scripts/gendwarfksyms/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
|
/gendwarfksyms
|
8
scripts/gendwarfksyms/Makefile
Normal file
8
scripts/gendwarfksyms/Makefile
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
|
hostprogs-always-y += gendwarfksyms
|
||||||
|
|
||||||
|
gendwarfksyms-objs += gendwarfksyms.o
|
||||||
|
gendwarfksyms-objs += dwarf.o
|
||||||
|
gendwarfksyms-objs += symbols.o
|
||||||
|
|
||||||
|
HOSTLDLIBS_gendwarfksyms := -ldw -lelf
|
166
scripts/gendwarfksyms/dwarf.c
Normal file
166
scripts/gendwarfksyms/dwarf.c
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2024 Google LLC
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "gendwarfksyms.h"
|
||||||
|
|
||||||
|
static bool get_ref_die_attr(Dwarf_Die *die, unsigned int id, Dwarf_Die *value)
|
||||||
|
{
|
||||||
|
Dwarf_Attribute da;
|
||||||
|
|
||||||
|
/* dwarf_formref_die returns a pointer instead of an error value. */
|
||||||
|
return dwarf_attr(die, id, &da) && dwarf_formref_die(&da, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define DEFINE_GET_STRING_ATTR(attr) \
|
||||||
|
static const char *get_##attr##_attr(Dwarf_Die *die) \
|
||||||
|
{ \
|
||||||
|
Dwarf_Attribute da; \
|
||||||
|
if (dwarf_attr(die, DW_AT_##attr, &da)) \
|
||||||
|
return dwarf_formstring(&da); \
|
||||||
|
return NULL; \
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_GET_STRING_ATTR(name)
|
||||||
|
DEFINE_GET_STRING_ATTR(linkage_name)
|
||||||
|
|
||||||
|
static const char *get_symbol_name(Dwarf_Die *die)
|
||||||
|
{
|
||||||
|
const char *name;
|
||||||
|
|
||||||
|
/* rustc uses DW_AT_linkage_name for exported symbols */
|
||||||
|
name = get_linkage_name_attr(die);
|
||||||
|
if (!name)
|
||||||
|
name = get_name_attr(die);
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool match_export_symbol(struct state *state, Dwarf_Die *die)
|
||||||
|
{
|
||||||
|
Dwarf_Die *source = die;
|
||||||
|
Dwarf_Die origin;
|
||||||
|
|
||||||
|
/* If the DIE has an abstract origin, use it for type information. */
|
||||||
|
if (get_ref_die_attr(die, DW_AT_abstract_origin, &origin))
|
||||||
|
source = &origin;
|
||||||
|
|
||||||
|
state->sym = symbol_get(get_symbol_name(die));
|
||||||
|
|
||||||
|
/* Look up using the origin name if there are no matches. */
|
||||||
|
if (!state->sym && source != die)
|
||||||
|
state->sym = symbol_get(get_symbol_name(source));
|
||||||
|
|
||||||
|
state->die = *source;
|
||||||
|
return !!state->sym;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Type string processing
|
||||||
|
*/
|
||||||
|
static void process(const char *s)
|
||||||
|
{
|
||||||
|
s = s ?: "<null>";
|
||||||
|
|
||||||
|
if (dump_dies)
|
||||||
|
fputs(s, stderr);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool match_all(Dwarf_Die *die)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int process_die_container(struct state *state, Dwarf_Die *die,
|
||||||
|
die_callback_t func, die_match_callback_t match)
|
||||||
|
{
|
||||||
|
Dwarf_Die current;
|
||||||
|
int res;
|
||||||
|
|
||||||
|
res = checkp(dwarf_child(die, ¤t));
|
||||||
|
while (!res) {
|
||||||
|
if (match(¤t)) {
|
||||||
|
/* <0 = error, 0 = continue, >0 = stop */
|
||||||
|
res = checkp(func(state, ¤t));
|
||||||
|
if (res)
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
res = checkp(dwarf_siblingof(¤t, ¤t));
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Exported symbol processing
|
||||||
|
*/
|
||||||
|
static void process_symbol(struct state *state, Dwarf_Die *die,
|
||||||
|
die_callback_t process_func)
|
||||||
|
{
|
||||||
|
debug("%s", state->sym->name);
|
||||||
|
check(process_func(state, die));
|
||||||
|
if (dump_dies)
|
||||||
|
fputs("\n", stderr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int __process_subprogram(struct state *state, Dwarf_Die *die)
|
||||||
|
{
|
||||||
|
process("subprogram");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void process_subprogram(struct state *state, Dwarf_Die *die)
|
||||||
|
{
|
||||||
|
process_symbol(state, die, __process_subprogram);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int __process_variable(struct state *state, Dwarf_Die *die)
|
||||||
|
{
|
||||||
|
process("variable ");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void process_variable(struct state *state, Dwarf_Die *die)
|
||||||
|
{
|
||||||
|
process_symbol(state, die, __process_variable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int process_exported_symbols(struct state *unused, Dwarf_Die *die)
|
||||||
|
{
|
||||||
|
int tag = dwarf_tag(die);
|
||||||
|
|
||||||
|
switch (tag) {
|
||||||
|
/* Possible containers of exported symbols */
|
||||||
|
case DW_TAG_namespace:
|
||||||
|
case DW_TAG_class_type:
|
||||||
|
case DW_TAG_structure_type:
|
||||||
|
return check(process_die_container(
|
||||||
|
NULL, die, process_exported_symbols, match_all));
|
||||||
|
|
||||||
|
/* Possible exported symbols */
|
||||||
|
case DW_TAG_subprogram:
|
||||||
|
case DW_TAG_variable: {
|
||||||
|
struct state state;
|
||||||
|
|
||||||
|
if (!match_export_symbol(&state, die))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (tag == DW_TAG_subprogram)
|
||||||
|
process_subprogram(&state, &state.die);
|
||||||
|
else
|
||||||
|
process_variable(&state, &state.die);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void process_cu(Dwarf_Die *cudie)
|
||||||
|
{
|
||||||
|
check(process_die_container(NULL, cudie, process_exported_symbols,
|
||||||
|
match_all));
|
||||||
|
}
|
128
scripts/gendwarfksyms/gendwarfksyms.c
Normal file
128
scripts/gendwarfksyms/gendwarfksyms.c
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2024 Google LLC
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <getopt.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include "gendwarfksyms.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Options
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Print debugging information to stderr */
|
||||||
|
int debug;
|
||||||
|
/* Dump DIE contents */
|
||||||
|
int dump_dies;
|
||||||
|
|
||||||
|
static void usage(void)
|
||||||
|
{
|
||||||
|
fputs("Usage: gendwarfksyms [options] elf-object-file ... < symbol-list\n\n"
|
||||||
|
"Options:\n"
|
||||||
|
" -d, --debug Print debugging information\n"
|
||||||
|
" --dump-dies Dump DWARF DIE contents\n"
|
||||||
|
" -h, --help Print this message\n"
|
||||||
|
"\n",
|
||||||
|
stderr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int process_module(Dwfl_Module *mod, void **userdata, const char *name,
|
||||||
|
Dwarf_Addr base, void *arg)
|
||||||
|
{
|
||||||
|
Dwarf_Addr dwbias;
|
||||||
|
Dwarf_Die cudie;
|
||||||
|
Dwarf_CU *cu = NULL;
|
||||||
|
Dwarf *dbg;
|
||||||
|
int res;
|
||||||
|
|
||||||
|
debug("%s", name);
|
||||||
|
dbg = dwfl_module_getdwarf(mod, &dwbias);
|
||||||
|
|
||||||
|
do {
|
||||||
|
res = dwarf_get_units(dbg, cu, &cu, NULL, NULL, &cudie, NULL);
|
||||||
|
if (res < 0)
|
||||||
|
error("dwarf_get_units failed: no debugging information?");
|
||||||
|
if (res == 1)
|
||||||
|
break; /* No more units */
|
||||||
|
|
||||||
|
process_cu(&cudie);
|
||||||
|
} while (cu);
|
||||||
|
|
||||||
|
return DWARF_CB_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const Dwfl_Callbacks callbacks = {
|
||||||
|
.section_address = dwfl_offline_section_address,
|
||||||
|
.find_debuginfo = dwfl_standard_find_debuginfo,
|
||||||
|
};
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
unsigned int n;
|
||||||
|
int opt;
|
||||||
|
|
||||||
|
static const struct option opts[] = {
|
||||||
|
{ "debug", 0, NULL, 'd' },
|
||||||
|
{ "dump-dies", 0, &dump_dies, 1 },
|
||||||
|
{ "help", 0, NULL, 'h' },
|
||||||
|
{ 0, 0, NULL, 0 }
|
||||||
|
};
|
||||||
|
|
||||||
|
while ((opt = getopt_long(argc, argv, "dh", opts, NULL)) != EOF) {
|
||||||
|
switch (opt) {
|
||||||
|
case 0:
|
||||||
|
break;
|
||||||
|
case 'd':
|
||||||
|
debug = 1;
|
||||||
|
break;
|
||||||
|
case 'h':
|
||||||
|
usage();
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
usage();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (optind >= argc) {
|
||||||
|
usage();
|
||||||
|
error("no input files?");
|
||||||
|
}
|
||||||
|
|
||||||
|
symbol_read_exports(stdin);
|
||||||
|
|
||||||
|
for (n = optind; n < argc; n++) {
|
||||||
|
Dwfl *dwfl;
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
fd = open(argv[n], O_RDONLY);
|
||||||
|
if (fd == -1)
|
||||||
|
error("open failed for '%s': %s", argv[n],
|
||||||
|
strerror(errno));
|
||||||
|
|
||||||
|
dwfl = dwfl_begin(&callbacks);
|
||||||
|
if (!dwfl)
|
||||||
|
error("dwfl_begin failed for '%s': %s", argv[n],
|
||||||
|
dwarf_errmsg(-1));
|
||||||
|
|
||||||
|
if (!dwfl_report_offline(dwfl, argv[n], argv[n], fd))
|
||||||
|
error("dwfl_report_offline failed for '%s': %s",
|
||||||
|
argv[n], dwarf_errmsg(-1));
|
||||||
|
|
||||||
|
dwfl_report_end(dwfl, NULL, NULL);
|
||||||
|
|
||||||
|
if (dwfl_getmodules(dwfl, &process_module, NULL, 0))
|
||||||
|
error("dwfl_getmodules failed for '%s'", argv[n]);
|
||||||
|
|
||||||
|
dwfl_end(dwfl);
|
||||||
|
}
|
||||||
|
|
||||||
|
symbol_free();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
95
scripts/gendwarfksyms/gendwarfksyms.h
Normal file
95
scripts/gendwarfksyms/gendwarfksyms.h
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0 */
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2024 Google LLC
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <dwarf.h>
|
||||||
|
#include <elfutils/libdw.h>
|
||||||
|
#include <elfutils/libdwfl.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include <hash.h>
|
||||||
|
#include <hashtable.h>
|
||||||
|
#include <xalloc.h>
|
||||||
|
|
||||||
|
#ifndef __GENDWARFKSYMS_H
|
||||||
|
#define __GENDWARFKSYMS_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Options -- in gendwarfksyms.c
|
||||||
|
*/
|
||||||
|
extern int debug;
|
||||||
|
extern int dump_dies;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Output helpers
|
||||||
|
*/
|
||||||
|
#define __PREFIX "gendwarfksyms: "
|
||||||
|
#define __println(prefix, format, ...) \
|
||||||
|
fprintf(stderr, prefix __PREFIX "%s: " format "\n", __func__, \
|
||||||
|
##__VA_ARGS__)
|
||||||
|
|
||||||
|
#define debug(format, ...) \
|
||||||
|
do { \
|
||||||
|
if (debug) \
|
||||||
|
__println("", format, ##__VA_ARGS__); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define warn(format, ...) __println("warning: ", format, ##__VA_ARGS__)
|
||||||
|
#define error(format, ...) \
|
||||||
|
do { \
|
||||||
|
__println("error: ", format, ##__VA_ARGS__); \
|
||||||
|
exit(1); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Error handling helpers
|
||||||
|
*/
|
||||||
|
#define __check(expr, test) \
|
||||||
|
({ \
|
||||||
|
int __res = expr; \
|
||||||
|
if (test) \
|
||||||
|
error("`%s` failed: %d", #expr, __res); \
|
||||||
|
__res; \
|
||||||
|
})
|
||||||
|
|
||||||
|
/* Error == non-zero values */
|
||||||
|
#define check(expr) __check(expr, __res)
|
||||||
|
/* Error == negative values */
|
||||||
|
#define checkp(expr) __check(expr, __res < 0)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* symbols.c
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct symbol {
|
||||||
|
const char *name;
|
||||||
|
struct hlist_node name_hash;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef void (*symbol_callback_t)(struct symbol *, void *arg);
|
||||||
|
|
||||||
|
void symbol_read_exports(FILE *file);
|
||||||
|
struct symbol *symbol_get(const char *name);
|
||||||
|
void symbol_free(void);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* dwarf.c
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct state {
|
||||||
|
struct symbol *sym;
|
||||||
|
Dwarf_Die die;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef int (*die_callback_t)(struct state *state, Dwarf_Die *die);
|
||||||
|
typedef bool (*die_match_callback_t)(Dwarf_Die *die);
|
||||||
|
bool match_all(Dwarf_Die *die);
|
||||||
|
|
||||||
|
int process_die_container(struct state *state, Dwarf_Die *die,
|
||||||
|
die_callback_t func, die_match_callback_t match);
|
||||||
|
|
||||||
|
void process_cu(Dwarf_Die *cudie);
|
||||||
|
|
||||||
|
#endif /* __GENDWARFKSYMS_H */
|
98
scripts/gendwarfksyms/symbols.c
Normal file
98
scripts/gendwarfksyms/symbols.c
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2024 Google LLC
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "gendwarfksyms.h"
|
||||||
|
|
||||||
|
#define SYMBOL_HASH_BITS 12
|
||||||
|
|
||||||
|
/* name -> struct symbol */
|
||||||
|
static HASHTABLE_DEFINE(symbol_names, 1 << SYMBOL_HASH_BITS);
|
||||||
|
|
||||||
|
static unsigned int for_each(const char *name, symbol_callback_t func,
|
||||||
|
void *data)
|
||||||
|
{
|
||||||
|
struct hlist_node *tmp;
|
||||||
|
struct symbol *match;
|
||||||
|
|
||||||
|
if (!name || !*name)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
hash_for_each_possible_safe(symbol_names, match, tmp, name_hash,
|
||||||
|
hash_str(name)) {
|
||||||
|
if (strcmp(match->name, name))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (func)
|
||||||
|
func(match, data);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_exported(const char *name)
|
||||||
|
{
|
||||||
|
return for_each(name, NULL, NULL) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void symbol_read_exports(FILE *file)
|
||||||
|
{
|
||||||
|
struct symbol *sym;
|
||||||
|
char *line = NULL;
|
||||||
|
char *name = NULL;
|
||||||
|
size_t size = 0;
|
||||||
|
int nsym = 0;
|
||||||
|
|
||||||
|
while (getline(&line, &size, file) > 0) {
|
||||||
|
if (sscanf(line, "%ms\n", &name) != 1)
|
||||||
|
error("malformed input line: %s", line);
|
||||||
|
|
||||||
|
if (is_exported(name)) {
|
||||||
|
/* Ignore duplicates */
|
||||||
|
free(name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
sym = xcalloc(1, sizeof(struct symbol));
|
||||||
|
sym->name = name;
|
||||||
|
|
||||||
|
hash_add(symbol_names, &sym->name_hash, hash_str(sym->name));
|
||||||
|
++nsym;
|
||||||
|
|
||||||
|
debug("%s", sym->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(line);
|
||||||
|
debug("%d exported symbols", nsym);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void get_symbol(struct symbol *sym, void *arg)
|
||||||
|
{
|
||||||
|
struct symbol **res = arg;
|
||||||
|
|
||||||
|
*res = sym;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct symbol *symbol_get(const char *name)
|
||||||
|
{
|
||||||
|
struct symbol *sym = NULL;
|
||||||
|
|
||||||
|
for_each(name, get_symbol, &sym);
|
||||||
|
return sym;
|
||||||
|
}
|
||||||
|
|
||||||
|
void symbol_free(void)
|
||||||
|
{
|
||||||
|
struct hlist_node *tmp;
|
||||||
|
struct symbol *sym;
|
||||||
|
|
||||||
|
hash_for_each_safe(symbol_names, sym, tmp, name_hash) {
|
||||||
|
free((void *)sym->name);
|
||||||
|
free(sym);
|
||||||
|
}
|
||||||
|
|
||||||
|
hash_init(symbol_names);
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user