-- Permission is hereby granted, free of charge, to any person -- obtaining a copy of this software and associated documentation files -- (the "Software"), to deal in the Software without restriction, -- including without limitation the rights to use, copy, modify, merge, -- publish, distribute, sublicense, and/or sell copies of the Software, -- and to permit persons to whom the Software is furnished to do so, -- subject to the following conditions: -- -- The above copyright notice and this permission notice shall be -- included in all copies or substantial portions of the Software. -- -- 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 OR COPYRIGHT HOLDERS -- 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. --based on callgraph from http://publicclu2.blogspot.co.uk/2013/05/call-graph-generation.html --translated to lua by Domingo Alvarez Duarte mingodad_at_gmail_dot_com local strFormat = string.format local function capture(cmd) local f = assert(io.popen(cmd, 'r')) local s = assert(f:read('*a')) f:close() return s end local prog_name="callgraph" if #arg < 1 then print( strFormat("Usage: %s EXECUTABLE [ARGS...]", prog_name)) print( strFormat("\nExample: %s ~/bin/test-program foo 23", prog_name)) os.exit( 1 ) end -- Sanity checks. local FILE=arg[1] local fd, err = io.open(FILE) if err then print(strFormat("%s: Unable to find executable '%s'", prog_name, FILE)) os.exit( 1 ) end fd:close() local str = capture(strFormat("gdb --eval-command=quit %s 2>&1", FILE)) if str:match("(no debugging symbols found)") or str:match("(not in executable format)") then print(strFormat([==[ %s: Can't print call graph for '%s' because it's not a binary executable compiled with debugging symbols. ]==], prog_name, FILE)) os.exit( 1 ) end -- Set up temporary files. local TRACE_file_name = FILE .. ".gdb.trace" local TRACE_fd=io.open(TRACE_file_name, "w") local GETFUNCS_file_name = FILE .. ".gdb.funcs" local GETFUNCS_fd=io.open(GETFUNCS_file_name, "w") -- Take control of GDB and print call graph. GETFUNCS_fd:write([[ set height 0 info functions ]]) GETFUNCS_fd:close() str = capture(strFormat("gdb --batch --command=%s %s 2>/dev/null", GETFUNCS_file_name, FILE)) --print(str) --os.exit(1) local total = 0; TRACE_fd:write([[ set width 0 set height 0 set verbose off ]]) local functions_to_break = {} for fn in str:gmatch("([a-zA-Z_][a-zA-Z0-9_]+)%b()") do TRACE_fd:write(strFormat("break %s\n", fn)); functions_to_break[fn] = 0; total = total + 1; end for i = 1, total do TRACE_fd:write(strFormat("commands %d\n", i)); --TRACE_fd:write("info args\n"); TRACE_fd:write( "backtrace 2\ncontinue\nend\n"); end TRACE_fd:write( "run\n"); TRACE_fd:close() local cmd_args = "" for i=2, #arg do cmd_args = cmd_args .. " " .. arg[i] end --print(FILE, cmd_args) local cmd = strFormat("gdb --batch --command=%s --tty=/dev/null --args %s %s 2>/dev/null", TRACE_file_name, FILE, cmd_args) --print(cmd) local fd_cmd = assert(io.popen(cmd, 'r')) local function joinLines(s) if not s:match(":%d+$") then --parameters have embedded new lines while(true) do local new_line = fd_cmd:read('*l') if not new_line then break end s = s .. "\\n" .. new_line if new_line:match(":%d+$") then break end end end return s end local isrecord = false; local callee = ""; local caller = "*INITIAL*"; local params = ""; while(true) do local s = fd_cmd:read('*l') if not s then break end --print(s) if s:match("^Breakpoint [0-9]+,") then isrecord = true; s = joinLines(s) callee, params = s:match("^Breakpoint [0-9]+,%s*(%S+)%s*(%(.*%))%s+at%s+%S+:%d+$"); local call_count = functions_to_break[callee] if call_count then functions_to_break[callee] = call_count + 1 else print("Bad match", callee, s) end elseif s:match("^#1%s+") then if (isrecord) then s = joinLines(s) caller =s:match("^#1%s+%S+%s+%S+%s+(%S+)"); end elseif #s == 0 then if (isrecord and (caller ~= "*INITIAL*")) then print(strFormat("%s %s %s", caller, callee, params)); callee = ""; caller = ""; params = ""; end else --print("No match line", s) end end fd_cmd:close() local functions_calls = {} for fn, cnt in pairs(functions_to_break) do table.insert(functions_calls, {fn, cnt}) end --table.sort(functions_calls, function(a,b) return a[2] > b[2] end) table.sort(functions_calls, function(a,b) if a[2] == b[2] then return a[1] < b[1] end return a[2] > b[2] end) print("Function calls count") for k,v in ipairs(functions_calls) do print(v[1], v[2]) end