]> git.proxmox.com Git - mirror_frr.git/commitdiff
tools: grok struct member calls in frr-llvm-cg
authorDavid Lamparter <equinox@diac24.net>
Tue, 23 Mar 2021 19:19:10 +0000 (20:19 +0100)
committerDavid Lamparter <equinox@diac24.net>
Wed, 7 Apr 2021 20:31:25 +0000 (22:31 +0200)
Calling a function pointer embedded in a struct is quite common & having
this listed in the call graph is useful.

Signed-off-by: David Lamparter <equinox@diac24.net>
tools/frr-llvm-cg.c
tools/frr-llvm-debuginfo.cpp [new file with mode: 0644]
tools/frr-llvm-debuginfo.h [new file with mode: 0644]
tools/subdir.am

index 5bdd10d06d2dc697688a25a4c7659210b393d48f..81468b73407bdbb6b4398f630cccef25be8576bb 100644 (file)
 
 #include <json-c/json.h>
 
+#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) {
@@ -280,6 +376,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);
@@ -330,7 +432,6 @@ static void process_call(struct json_object *js_calls,
                        fprintf(stderr, "%s: ??? %s\n", prefix, dump);
                        LLVMDisposeMessage(dump);
                }
-               return;
 #ifdef FRR_SPECIFIC
        } else if (!strcmp(called_name, "_install_element")) {
                called_type = FN_INSTALL_ELEMENT;
@@ -440,6 +541,7 @@ static void process_call(struct json_object *js_calls,
                        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 +699,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 (file)
index 0000000..ed3ad95
--- /dev/null
@@ -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 <http://unlicense.org/>
+
+#include <llvm-c/BitReader.h>
+#include <llvm-c/BitWriter.h>
+#include <llvm-c/Core.h>
+#include <llvm-c/DebugInfo.h>
+
+#include <llvm/IR/Module.h>
+#include <llvm/IR/Value.h>
+#include <llvm/IR/Type.h>
+#include <llvm/IR/DebugInfo.h>
+#include <llvm/IR/DebugInfoMetadata.h>
+#include <llvm/Support/raw_ostream.h>
+
+#include <map>
+
+#include "frr-llvm-debuginfo.h"
+
+/* llvm::DebugInfoFinder is unfortunately not exposed in the llvm-c API... */
+
+struct dbginfo {
+       llvm::DebugInfoFinder finder;
+       std::map<std::string, llvm::DICompositeType *> 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 (file)
index 0000000..fca4bc1
--- /dev/null
@@ -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 <http://unlicense.org/>
+
+#ifndef _FRR_LLVM_DEBUGINFO_H
+#define _FRR_LLVM_DEBUGINFO_H
+
+#include <stdbool.h>
+#include <llvm-c/Core.h>
+
+#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 */
index e159d82d4cd2d22d01dc403ea651a17f620d4a7e..027763cf91f2f030485a3224526640ffbef636cf 100644 (file)
@@ -38,9 +38,15 @@ tools_ssd_SOURCES = tools/start-stop-daemon.c
 # don't bother autoconf'ing these for a simple optional tool
 llvm_version = $(shell echo __clang_major__ | $(CC) -xc -P -E -)
 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 += \