#!/usr/local/bin/recon local CoreSymbolication = require 'CoreSymbolication' local argparse = require 'argparse' local kperf = require 'kperf' local ktrace = require 'ktrace' local strict = require 'strict' local ksancov = {} -- KCOV event ksancov.Event = {} ksancov.Event.__index = ksancov.Event function ksancov.Event.new() local instance = { pid = nil, procname = nil, tid = nil, stack_size = nil, pc = nil, duration = nil, ustack = nil, kstack = nil, } return setmetatable(instance, ksancov.Event) end function ksancov.Event:print() print(string.format("%s(%d)\t%d\t at %x has stack size %d.", self.procname, self.pid, self.tid, self.pc, self.stack_size)) end -- KSANCOV tracing session ksancov.Session = {} ksancov.Session.__index = ksancov.Session function ksancov.Session.new(ktrace_session, flags) assert(ktrace_session) local instance = { ktrace_session = ktrace_session, kperf_session = kperf.Session.new(ktrace_session), events_by_tid = {}, event_handlers = {}, } local self = setmetatable(instance, ksancov.Session) self:_register_kperf_callbacks(flags.ustack, flags.kstack) self:_register_ktrace_callbacks(flags.proc) return self end function ksancov.Session:start() return self.ktrace_session:start() end function ksancov.Session:_register_kperf_callbacks(ustack, kstack) local samplers = {} if kstack then table.insert(samplers, 'kstack') end if ustack then table.insert(samplers, 'ustack') end -- D0x01ad0000 KCOV_THRESHOLD_ABOVE if #samplers == 0 then return end self.kperf_session:add_kdebug_sampler({'D0x01ad0000'}, samplers) -- Collect userspace stacks if ustack then self.kperf_session:add_callback_sample({'ustack'}, function (sample) local event = self.events_by_tid[sample.threadid] if event then event.ustack = sample.ustack end end) end -- Collect kernel stacks if kstack then self.kperf_session:add_callback_sample({'kstack'}, function (sample) local event = self.events_by_tid[sample.threadid] if event then event.kstack = sample.kstack end end) end end function ksancov.Session:_register_ktrace_callbacks(proc) self.ktrace_session:add_callback('KCOV_STKSZ_THRESHOLD_ABOVE', function (trace_point) self:_handle_threshold_above(ktrace.copy_event(trace_point)) end) self.ktrace_session:add_callback('KCOV_STKSZ_THRESHOLD_BELOW', function (trace_point) self:_handle_threshold_below(ktrace.copy_event(trace_point)) end) if proc then self.ktrace_session:filter_proc(proc) end end function ksancov.Session:_handle_threshold_above(trace_point) local event = ksancov.Event.new() event.tid = trace_point["threadid"] event.pid = self.ktrace_session:pid_for_threadid(event.tid) or "0" event.pc = trace_point[1] event.stack_size = trace_point[2] event.procname = self.ktrace_session:procname_for_threadid(event.tid) self.events_by_tid[event.tid] = event end function ksancov.Session:_handle_threshold_below(trace_point) local event = self.events_by_tid[trace_point["threadid"]] -- It is possible that we redord BELOW event as first. Ignore it if we haven't seen -- the ABOVE event first. if event then self.events_by_tid[event.tid] = nil for i, handler in pairs(self.event_handlers) do handler(event) end end end function ksancov.Session:add_event_handler(handler) table.insert(self.event_handlers, handler) end -- Utility code local function parse_args() local parser = argparse("ksancov", "Kernel stack size monitoring utility.") parser:option { name = "--codes-files", description = "Import debugid-to-string mapping files", args = "*", count = "?", } parser:option { name = "-f --file", description = "artrace or ktrace file to read from", args = 1, count = "?", } parser:option { name = "-p --proc", description = "pid or process name to be recorded", args = 1, count = "?", } parser:flag { name = "-u --ustack", description = "sample user space stacks", count = "?", } parser:flag { name = "-k --kstack", description = "sample kernel space stacks", count = "?", } return parser:parse(arg) end local flags = parse_args() local ktrace_session = ktrace.Session.new(flags.file) if flags.codes_files then for _, file in pairs(flags.codes_files) do ktrace_session:add_codes_file(file) end end local ksancov_session = ksancov.Session.new(ktrace_session, flags) ksancov_session:add_event_handler(function (event) event:print() local function symbolicate(symbolicator, frame) local symbol = symbolicator:symbolicate(frame) if symbol then print(("\t%s (in %s)"):format(symbol.name or "???", symbol.owner_name or "???")) else print(("\t0x%x (in ???)").format(frame)) end end -- Symbolicate stacks if event.ustack then print('ustack:') if flags.file then -- When reading from a file, we can't use CoreSymbolication to symbolicate the stack frames as the processes are -- not actually running, so use our ktrace session instead. for _, frame in ipairs(event.ustack or {}) do print((" %s"):format(ktrace_session:symbolicate_with_pid(event.pid, frame))) end else local symbolicator = CoreSymbolication.Symbolicator.new(event.pid) if symbolicator then for _, frame in ipairs(event.ustack or {}) do symbolicate(symbolicator, frame) end end end end if event.kstack then print('kstack:') if flags.file then -- When reading from a file, we can't use CoreSymbolication to symbolicate the stack frames as the processes are -- not actually running, so use our ktrace session instead. for _, frame in ipairs(event.kstack or {}) do print((" %s"):format(ktrace_session:symbolicate_with_pid(event.pid, frame))) end else local symbolicator = CoreSymbolication.Symbolicator.new(0) if symbolicator then for _, frame in ipairs(event.kstack or {}) do symbolicate(symbolicator, frame) end end end end end) io.stdout:write("Started recording ...\n") local ok, err = ksancov_session:start() if not ok then io.stderr:write("Failed to start session: ", err, "\n") os.exit(1) end io.stdout:write("DONE.\n")