diff --git a/.gitignore b/.gitignore index 4e120dae85..33d03296db 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ /libtool.orig /changelog-auto /test-driver +/test-suite.log /Makefile /Makefile.in @@ -57,6 +58,7 @@ *.pb.cc *_clippy.c *.bc +*.ll *.cg.json *.cg.dot *.cg.svg diff --git a/lib/prefix.h b/lib/prefix.h index d7ee1b8e4c..217a23d561 100644 --- a/lib/prefix.h +++ b/lib/prefix.h @@ -315,10 +315,12 @@ struct prefix_sg { #ifndef __cplusplus #define prefixtype(uname, typename, fieldname) \ typename *fieldname; +#define TRANSPARENT_UNION __attribute__((transparent_union)) #else #define prefixtype(uname, typename, fieldname) \ typename *fieldname; \ uname(typename *x) { this->fieldname = x; } +#define TRANSPARENT_UNION #endif union prefixptr { @@ -328,7 +330,7 @@ union prefixptr { prefixtype(prefixptr, struct prefix_evpn, evp) prefixtype(prefixptr, struct prefix_fs, fs) prefixtype(prefixptr, struct prefix_rd, rd) -} __attribute__((transparent_union)); +} TRANSPARENT_UNION; union prefixconstptr { prefixtype(prefixconstptr, const struct prefix, p) @@ -337,7 +339,10 @@ union prefixconstptr { prefixtype(prefixconstptr, const struct prefix_evpn, evp) prefixtype(prefixconstptr, const struct prefix_fs, fs) prefixtype(prefixconstptr, const struct prefix_rd, rd) -} __attribute__((transparent_union)); +} TRANSPARENT_UNION; + +#undef prefixtype +#undef TRANSPARENT_UNION #ifndef INET_ADDRSTRLEN #define INET_ADDRSTRLEN 16 diff --git a/tools/.gitignore b/tools/.gitignore index 63a5b61c35..1cc343a11a 100644 --- a/tools/.gitignore +++ b/tools/.gitignore @@ -8,3 +8,4 @@ /frrcommon.sh /frr.service /frr@.service +/frr-llvm-cg diff --git a/tools/frr-llvm-cg.c b/tools/frr-llvm-cg.c index 84a756a376..fbcf9222d2 100644 --- a/tools/frr-llvm-cg.c +++ b/tools/frr-llvm-cg.c @@ -50,11 +50,15 @@ #include +#include "frr-llvm-debuginfo.h" + /* if you want to use this without the special FRRouting defines, * remove the following #define */ #define FRR_SPECIFIC +static struct dbginfo *dbginfo; + static void dbgloc_add(struct json_object *jsobj, LLVMValueRef obj) { unsigned file_len = 0; @@ -85,6 +89,70 @@ static struct json_object *js_get_or_make(struct json_object *parent, return ret; } +static bool try_struct_fptr(struct json_object *js_call, LLVMValueRef gep, + const char *prefix) +{ + unsigned long long val = 0; + bool ret = false; + LLVMTypeRef ptrtype = LLVMTypeOf(LLVMGetOperand(gep, 0)); + LLVMValueRef idx; + + /* middle steps like struct a -> struct b a_member; -> fptr */ + for (int i = 1; ptrtype && i < LLVMGetNumOperands(gep) - 1; i++) { + if (LLVMGetTypeKind(ptrtype) == LLVMPointerTypeKind + || LLVMGetTypeKind(ptrtype) == LLVMArrayTypeKind + || LLVMGetTypeKind(ptrtype) == LLVMVectorTypeKind) { + ptrtype = LLVMGetElementType(ptrtype); + continue; + } + + if (LLVMGetTypeKind(ptrtype) != LLVMStructTypeKind) + return false; + + idx = LLVMGetOperand(gep, i); + if (!LLVMIsConstant(idx)) + return false; + val = LLVMConstIntGetZExtValue(idx); + + unsigned n = LLVMGetNumContainedTypes(ptrtype); + LLVMTypeRef arr[n]; + + if (val > n) + return false; + + LLVMGetSubtypes(ptrtype, arr); + ptrtype = arr[val]; + } + + if (!ptrtype) + return false; + + idx = LLVMGetOperand(gep, LLVMGetNumOperands(gep) - 1); + if (!LLVMIsConstant(idx)) + return false; + + val = LLVMConstIntGetZExtValue(idx); + + char *sname = NULL, *mname = NULL; + + if (dbginfo_struct_member(dbginfo, ptrtype, val, &sname, &mname)) { + fprintf(stderr, "%s: call to struct %s->%s\n", prefix, sname, + mname); + + json_object_object_add(js_call, "type", + json_object_new_string("struct_memb")); + json_object_object_add(js_call, "struct", + json_object_new_string(sname)); + json_object_object_add(js_call, "member", + json_object_new_string(mname)); + ret = true; + } + free(sname); + free(mname); + + return ret; +} + static bool details_fptr_vars = false; static bool details_fptr_consts = true; @@ -175,6 +243,34 @@ static void walk_const_fptrs(struct json_object *js_call, LLVMValueRef value, prefix, hdr_written); return; + case LLVMConstantExprValueKind: + switch (LLVMGetConstOpcode(value)) { + case LLVMGetElementPtr: + if (try_struct_fptr(js_call, value, prefix)) { + *hdr_written = true; + return; + } + + fprintf(stderr, + "%s: calls function pointer from unhandled const GEP\n", + prefix); + *hdr_written = true; + /* fallthru */ + default: + /* to help the user / development */ + if (!*hdr_written) { + fprintf(stderr, + "%s: calls function pointer from constexpr\n", + prefix); + *hdr_written = true; + } + dump = LLVMPrintValueToString(value); + fprintf(stderr, "%s- [opcode=%d] %s\n", prefix, + LLVMGetConstOpcode(value), dump); + LLVMDisposeMessage(dump); + } + return; + default: /* to help the user / development */ if (!*hdr_written) { @@ -197,7 +293,7 @@ static void walk_const_fptrs(struct json_object *js_call, LLVMValueRef value, #ifdef FRR_SPECIFIC static bool is_thread_sched(const char *name, size_t len) { -#define thread_prefix "funcname_" +#define thread_prefix "_" static const char *const names[] = { thread_prefix "thread_add_read_write", thread_prefix "thread_add_timer", @@ -218,6 +314,225 @@ static bool is_thread_sched(const char *name, size_t len) } #endif +static bool _check_val(bool cond, const char *text, LLVMValueRef dumpval) +{ + if (cond) + return true; + + char *dump = LLVMPrintValueToString(dumpval); + fprintf(stderr, "check failed: %s\ndump:\n\t%s\n", text, dump); + LLVMDisposeMessage(dump); + return false; +} + +#define check_val(cond, dump) \ + if (!_check_val(cond, #cond, dump)) \ + return; + +static char *get_string(LLVMValueRef value) +{ + if (!LLVMIsAConstant(value)) + return strdup("!NOT-A-CONST"); + + if (LLVMGetValueKind(value) == LLVMConstantExprValueKind + && LLVMGetConstOpcode(value) == LLVMGetElementPtr) { + value = LLVMGetOperand(value, 0); + + if (!LLVMIsAConstant(value)) + return strdup("!NOT-A-CONST-2"); + } + + if (LLVMIsAGlobalVariable(value)) + value = LLVMGetInitializer(value); + + size_t len = 0; + const char *sval = LLVMGetAsString(value, &len); + + return strndup(sval, len); +} + +static void handle_yang_module(struct json_object *js_special, + LLVMValueRef yang_mod) +{ + check_val(LLVMIsAGlobalVariable(yang_mod), yang_mod); + + LLVMValueRef value; + + value = LLVMGetInitializer(yang_mod); + LLVMValueKind kind = LLVMGetValueKind(value); + + check_val(kind == LLVMConstantStructValueKind, value); + + size_t var_len = 0; + const char *var_name = LLVMGetValueName2(yang_mod, &var_len); + char buf_name[var_len + 1]; + + memcpy(buf_name, var_name, var_len); + buf_name[var_len] = '\0'; + + struct json_object *js_yang, *js_yangmod, *js_items; + + js_yang = js_get_or_make(js_special, "yang", json_object_new_object); + js_yangmod = js_get_or_make(js_yang, buf_name, json_object_new_object); + js_items = js_get_or_make(js_yangmod, "items", json_object_new_array); + + char *mod_name = get_string(LLVMGetOperand(value, 0)); + json_object_object_add(js_yangmod, "name", + json_object_new_string(mod_name)); + free(mod_name); + + value = LLVMGetOperand(value, 1); + kind = LLVMGetValueKind(value); + check_val(kind == LLVMConstantArrayValueKind, value); + + unsigned len = LLVMGetArrayLength(LLVMTypeOf(value)); + + for (unsigned i = 0; i < len - 1; i++) { + struct json_object *js_item, *js_cbs; + LLVMValueRef item = LLVMGetOperand(value, i); + char *xpath = get_string(LLVMGetOperand(item, 0)); + + js_item = json_object_new_object(); + json_object_array_add(js_items, js_item); + + json_object_object_add(js_item, "xpath", + json_object_new_string(xpath)); + js_cbs = js_get_or_make(js_item, "cbs", json_object_new_object); + + free(xpath); + + LLVMValueRef cbs = LLVMGetOperand(item, 1); + + check_val(LLVMGetValueKind(cbs) == LLVMConstantStructValueKind, + value); + + LLVMTypeRef cbs_type = LLVMTypeOf(cbs); + unsigned cblen = LLVMCountStructElementTypes(cbs_type); + + for (unsigned i = 0; i < cblen; i++) { + LLVMValueRef cb = LLVMGetOperand(cbs, i); + + char *sname = NULL; + char *mname = NULL; + + if (dbginfo_struct_member(dbginfo, cbs_type, i, &sname, + &mname)) { + (void)0; + } + + if (LLVMIsAFunction(cb)) { + size_t fn_len; + const char *fn_name; + + fn_name = LLVMGetValueName2(cb, &fn_len); + + json_object_object_add( + js_cbs, mname, + json_object_new_string_len(fn_name, + fn_len)); + } + + free(sname); + free(mname); + } + } +} + +static void handle_daemoninfo(struct json_object *js_special, + LLVMValueRef daemoninfo) +{ + check_val(LLVMIsAGlobalVariable(daemoninfo), daemoninfo); + + LLVMTypeRef type; + LLVMValueRef value; + unsigned len; + + type = LLVMGlobalGetValueType(daemoninfo); + value = LLVMGetInitializer(daemoninfo); + LLVMValueKind kind = LLVMGetValueKind(value); + + check_val(kind == LLVMConstantStructValueKind, value); + + int yang_idx = -1; + + len = LLVMCountStructElementTypes(type); + + LLVMTypeRef fieldtypes[len]; + LLVMGetSubtypes(type, fieldtypes); + + for (unsigned i = 0; i < len; i++) { + LLVMTypeRef t = fieldtypes[i]; + + if (LLVMGetTypeKind(t) != LLVMPointerTypeKind) + continue; + t = LLVMGetElementType(t); + if (LLVMGetTypeKind(t) != LLVMPointerTypeKind) + continue; + t = LLVMGetElementType(t); + if (LLVMGetTypeKind(t) != LLVMStructTypeKind) + continue; + + const char *name = LLVMGetStructName(t); + if (!strcmp(name, "struct.frr_yang_module_info")) + yang_idx = i; + } + + if (yang_idx == -1) + return; + + LLVMValueRef yang_mods = LLVMGetOperand(value, yang_idx); + LLVMValueRef yang_size = LLVMGetOperand(value, yang_idx + 1); + + check_val(LLVMIsConstant(yang_size), yang_size); + + unsigned long long ival = LLVMConstIntGetZExtValue(yang_size); + + check_val(LLVMGetValueKind(yang_mods) == LLVMConstantExprValueKind + && LLVMGetConstOpcode(yang_mods) == LLVMGetElementPtr, + yang_mods); + + yang_mods = LLVMGetOperand(yang_mods, 0); + + check_val(LLVMIsAGlobalVariable(yang_mods), yang_mods); + + yang_mods = LLVMGetInitializer(yang_mods); + + check_val(LLVMGetValueKind(yang_mods) == LLVMConstantArrayValueKind, + yang_mods); + + len = LLVMGetArrayLength(LLVMTypeOf(yang_mods)); + + if (len != ival) + fprintf(stderr, "length mismatch - %llu vs. %u\n", ival, len); + + for (unsigned i = 0; i < len; i++) { + char *dump; + + LLVMValueRef item = LLVMGetOperand(yang_mods, i); + LLVMValueKind kind = LLVMGetValueKind(item); + + check_val(kind == LLVMGlobalVariableValueKind + || kind == LLVMConstantExprValueKind, + item); + + if (kind == LLVMGlobalVariableValueKind) + continue; + + LLVMOpcode opcode = LLVMGetConstOpcode(item); + switch (opcode) { + case LLVMBitCast: + item = LLVMGetOperand(item, 0); + handle_yang_module(js_special, item); + break; + + default: + dump = LLVMPrintValueToString(item); + printf("[%u] = [opcode=%u] %s\n", i, opcode, dump); + LLVMDisposeMessage(dump); + } + } +} + static void process_call(struct json_object *js_calls, struct json_object *js_special, LLVMValueRef instr, @@ -227,6 +542,9 @@ static void process_call(struct json_object *js_calls, LLVMValueRef called = LLVMGetCalledValue(instr); + if (LLVMIsAInlineAsm(called)) + return; + if (LLVMIsAConstantExpr(called)) { LLVMOpcode opcode = LLVMGetConstOpcode(called); @@ -277,6 +595,12 @@ static void process_call(struct json_object *js_calls, snprintf(prefix, sizeof(prefix), "%.*s:%d:%.*s()", (int)file_len, file, line, (int)name_len, name_c); + if (LLVMIsALoadInst(called) + && LLVMIsAGetElementPtrInst(LLVMGetOperand(called, 0)) + && try_struct_fptr(js_call, LLVMGetOperand(called, 0), + prefix)) + goto out_struct_fptr; + while (LLVMIsALoadInst(last) || LLVMIsAGetElementPtrInst(last)) /* skipping over details for GEP here, but meh. */ last = LLVMGetOperand(last, 0); @@ -324,12 +648,11 @@ static void process_call(struct json_object *js_calls, prefix); } else { char *dump = LLVMPrintValueToString(called); - printf("\t%s\n", dump); + fprintf(stderr, "%s: ??? %s\n", prefix, dump); LLVMDisposeMessage(dump); } - return; #ifdef FRR_SPECIFIC - } else if (!strcmp(called_name, "install_element")) { + } else if (!strcmp(called_name, "_install_element")) { called_type = FN_INSTALL_ELEMENT; LLVMValueRef param0 = LLVMGetOperand(instr, 0); @@ -380,10 +703,7 @@ static void process_call(struct json_object *js_calls, json_object_new_string_len(called_name, called_len)); LLVMValueRef fparam; - if (strstr(called_name, "_read_")) - fparam = LLVMGetOperand(instr, 2); - else - fparam = LLVMGetOperand(instr, 1); + fparam = LLVMGetOperand(instr, 2); assert(fparam); size_t target_len = 0; @@ -434,12 +754,21 @@ static void process_call(struct json_object *js_calls, * - zclient->* ? */ #endif /* FRR_SPECIFIC */ + } else if (!strcmp(called_name, "frr_preinit")) { + LLVMValueRef daemoninfo = LLVMGetOperand(instr, 0); + + handle_daemoninfo(js_special, daemoninfo); + + json_object_object_add( + js_call, "target", + json_object_new_string_len(called_name, called_len)); } else { json_object_object_add( js_call, "target", json_object_new_string_len(called_name, called_len)); } +out_struct_fptr: for (unsigned argno = 0; argno < n_args; argno++) { LLVMValueRef param = LLVMGetOperand(instr, argno); size_t target_len; @@ -597,6 +926,8 @@ int main(int argc, char **argv) // done with the memory buffer now, so dispose of it LLVMDisposeMemoryBuffer(memoryBuffer); + dbginfo = dbginfo_load(module); + struct json_object *js_root, *js_funcs, *js_special; js_root = json_object_new_object(); diff --git a/tools/frr-llvm-debuginfo.cpp b/tools/frr-llvm-debuginfo.cpp new file mode 100644 index 0000000000..ed3ad956b8 --- /dev/null +++ b/tools/frr-llvm-debuginfo.cpp @@ -0,0 +1,112 @@ +// This is free and unencumbered software released into the public domain. +// +// Anyone is free to copy, modify, publish, use, compile, sell, or +// distribute this software, either in source code form or as a compiled +// binary, for any purpose, commercial or non-commercial, and by any +// means. +// +// In jurisdictions that recognize copyright laws, the author or authors +// of this software dedicate any and all copyright interest in the +// software to the public domain. We make this dedication for the benefit +// of the public at large and to the detriment of our heirs and +// successors. We intend this dedication to be an overt act of +// relinquishment in perpetuity of all present and future rights to this +// software under copyright law. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// For more information, please refer to + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "frr-llvm-debuginfo.h" + +/* llvm::DebugInfoFinder is unfortunately not exposed in the llvm-c API... */ + +struct dbginfo { + llvm::DebugInfoFinder finder; + std::map tab; +}; + +struct dbginfo *dbginfo_load(LLVMModuleRef _mod) +{ + llvm::Module *mod = llvm::unwrap(_mod); + struct dbginfo *info = new dbginfo(); + + info->finder.processModule(*mod); + + for (auto ty : info->finder.types()) { + if (ty->getMetadataID() != llvm::Metadata::DICompositeTypeKind) + continue; + + llvm::DICompositeType *cty = (llvm::DICompositeType *)ty; + /* empty forward declarations aka "struct foobar;" */ + if (cty->getElements().size() == 0) + continue; + + info->tab.emplace(std::move(ty->getName().str()), cty); + } + + return info; +} + +bool dbginfo_struct_member(struct dbginfo *info, LLVMTypeRef _typ, + unsigned long long idx, char **struct_name, + char **member_name) +{ + *struct_name = NULL; + *member_name = NULL; + + llvm::Type *typ = llvm::unwrap(_typ); + + if (!typ->isStructTy()) + return false; + + llvm::StructType *styp = (llvm::StructType *)typ; + auto sname = styp->getStructName(); + + if (!sname.startswith("struct.")) + return false; + sname = sname.drop_front(7); + + size_t dot = sname.find_last_of("."); + if (dot != sname.npos) + sname = sname.take_front(dot); + + auto item = info->tab.find(sname.str()); + if (item == info->tab.end()) + return false; + + auto elements = item->second->getElements(); + if (idx >= elements.size()) + return false; + + auto elem = elements[idx]; + + if (elem->getMetadataID() != llvm::Metadata::DIDerivedTypeKind) + return false; + + llvm::DIDerivedType *dtyp = (llvm::DIDerivedType *)elem; + + *struct_name = strdup(sname.str().c_str()); + *member_name = strdup(dtyp->getName().str().c_str()); + return true; +} diff --git a/tools/frr-llvm-debuginfo.h b/tools/frr-llvm-debuginfo.h new file mode 100644 index 0000000000..fca4bc1f97 --- /dev/null +++ b/tools/frr-llvm-debuginfo.h @@ -0,0 +1,47 @@ +// This is free and unencumbered software released into the public domain. +// +// Anyone is free to copy, modify, publish, use, compile, sell, or +// distribute this software, either in source code form or as a compiled +// binary, for any purpose, commercial or non-commercial, and by any +// means. +// +// In jurisdictions that recognize copyright laws, the author or authors +// of this software dedicate any and all copyright interest in the +// software to the public domain. We make this dedication for the benefit +// of the public at large and to the detriment of our heirs and +// successors. We intend this dedication to be an overt act of +// relinquishment in perpetuity of all present and future rights to this +// software under copyright law. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// For more information, please refer to + +#ifndef _FRR_LLVM_DEBUGINFO_H +#define _FRR_LLVM_DEBUGINFO_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct dbginfo; + +extern struct dbginfo *dbginfo_load(LLVMModuleRef mod); +extern bool dbginfo_struct_member(struct dbginfo *di, LLVMTypeRef typ, + unsigned long long idx, char **struct_name, + char **member_name); + +#ifdef __cplusplus +} +#endif + +#endif /* _FRR_LLVM_DEBUGINFO_H */ diff --git a/tools/subdir.am b/tools/subdir.am index 6a03a23baa..e4b9ecd84f 100644 --- a/tools/subdir.am +++ b/tools/subdir.am @@ -40,9 +40,15 @@ tools_ssd_CPPFLAGS = llvm_version = $(shell echo __clang_major__ | $(CC) -xc -P -E -) tools_frr_llvm_cg_CPPFLAGS = $(CPPFLAGS_BASE) tools_frr_llvm_cg_CFLAGS = $(AM_CFLAGS) `llvm-config-$(llvm_version) --cflags` +tools_frr_llvm_cg_CXXFLAGS = $(AM_CXXFLAGS) -O0 -ggdb3 `llvm-config-$(llvm_version) --cxxflags` tools_frr_llvm_cg_LDFLAGS = `llvm-config-$(llvm_version) --ldflags --libs` tools_frr_llvm_cg_SOURCES = \ tools/frr-llvm-cg.c \ + tools/frr-llvm-debuginfo.cpp \ + # end + +noinst_HEADERS += \ + tools/frr-llvm-debuginfo.h \ # end EXTRA_DIST += \