1#!/usr/local/bin/recon 2local CoreSymbolication = require 'CoreSymbolication' 3local argparse = require 'argparse' 4local kperf = require 'kperf' 5local ktrace = require 'ktrace' 6local strict = require 'strict' 7 8 9local ksancov = {} 10 11-- KCOV event 12 13ksancov.Event = {} 14ksancov.Event.__index = ksancov.Event 15 16function ksancov.Event.new() 17 local instance = { 18 pid = nil, 19 procname = nil, 20 tid = nil, 21 stack_size = nil, 22 pc = nil, 23 duration = nil, 24 ustack = nil, 25 kstack = nil, 26 } 27 28 return setmetatable(instance, ksancov.Event) 29end 30 31function ksancov.Event:print() 32 print(string.format("%s(%d)\t%d\t at %x has stack size %d.", self.procname, self.pid, self.tid, 33 self.pc, self.stack_size)) 34end 35 36-- KSANCOV tracing session 37 38ksancov.Session = {} 39ksancov.Session.__index = ksancov.Session 40 41function ksancov.Session.new(ktrace_session, flags) 42 assert(ktrace_session) 43 44 local instance = { 45 ktrace_session = ktrace_session, 46 kperf_session = kperf.Session.new(ktrace_session), 47 events_by_tid = {}, 48 event_handlers = {}, 49 } 50 51 local self = setmetatable(instance, ksancov.Session) 52 self:_register_kperf_callbacks(flags.ustack, flags.kstack) 53 self:_register_ktrace_callbacks(flags.proc) 54 return self 55end 56 57function ksancov.Session:start() 58 return self.ktrace_session:start() 59end 60 61function ksancov.Session:_register_kperf_callbacks(ustack, kstack) 62 local samplers = {} 63 if kstack then 64 table.insert(samplers, 'kstack') 65 end 66 67 if ustack then 68 table.insert(samplers, 'ustack') 69 end 70 71 -- D0x01ad0000 KCOV_THRESHOLD_ABOVE 72 if #samplers == 0 then 73 return 74 end 75 76 self.kperf_session:add_kdebug_sampler({'D0x01ad0000'}, samplers) 77 78 -- Collect userspace stacks 79 if ustack then 80 self.kperf_session:add_callback_sample({'ustack'}, function (sample) 81 local event = self.events_by_tid[sample.threadid] 82 if event then 83 event.ustack = sample.ustack 84 end 85 end) 86 end 87 88 -- Collect kernel stacks 89 if kstack then 90 self.kperf_session:add_callback_sample({'kstack'}, function (sample) 91 local event = self.events_by_tid[sample.threadid] 92 if event then 93 event.kstack = sample.kstack 94 end 95 end) 96 end 97end 98 99function ksancov.Session:_register_ktrace_callbacks(proc) 100 self.ktrace_session:add_callback('KCOV_STKSZ_THRESHOLD_ABOVE', function (trace_point) 101 self:_handle_threshold_above(ktrace.copy_event(trace_point)) 102 end) 103 104 self.ktrace_session:add_callback('KCOV_STKSZ_THRESHOLD_BELOW', function (trace_point) 105 self:_handle_threshold_below(ktrace.copy_event(trace_point)) 106 end) 107 108 if proc then 109 self.ktrace_session:filter_proc(proc) 110 end 111end 112 113function ksancov.Session:_handle_threshold_above(trace_point) 114 local event = ksancov.Event.new() 115 116 event.tid = trace_point["threadid"] 117 event.pid = self.ktrace_session:pid_for_threadid(event.tid) or "0" 118 event.pc = trace_point[1] 119 event.stack_size = trace_point[2] 120 event.procname = self.ktrace_session:procname_for_threadid(event.tid) 121 122 self.events_by_tid[event.tid] = event 123end 124 125function ksancov.Session:_handle_threshold_below(trace_point) 126 local event = self.events_by_tid[trace_point["threadid"]] 127 128 -- It is possible that we redord BELOW event as first. Ignore it if we haven't seen 129 -- the ABOVE event first. 130 if event then 131 self.events_by_tid[event.tid] = nil 132 133 for i, handler in pairs(self.event_handlers) do 134 handler(event) 135 end 136 end 137end 138 139function ksancov.Session:add_event_handler(handler) 140 table.insert(self.event_handlers, handler) 141end 142 143-- Utility code 144 145local function parse_args() 146 local parser = argparse("ksancov", "Kernel stack size monitoring utility.") 147 148 parser:option { 149 name = "--codes-files", 150 description = "Import debugid-to-string mapping files", 151 args = "*", 152 count = "?", 153 } 154 155 parser:option { 156 name = "-f --file", 157 description = "artrace or ktrace file to read from", 158 args = 1, 159 count = "?", 160 } 161 162 parser:option { 163 name = "-p --proc", 164 description = "pid or process name to be recorded", 165 args = 1, 166 count = "?", 167 } 168 169 parser:flag { 170 name = "-u --ustack", 171 description = "sample user space stacks", 172 count = "?", 173 } 174 175 parser:flag { 176 name = "-k --kstack", 177 description = "sample kernel space stacks", 178 count = "?", 179 } 180 181 return parser:parse(arg) 182end 183 184local flags = parse_args() 185local ktrace_session = ktrace.Session.new(flags.file) 186 187if flags.codes_files then 188 for _, file in pairs(flags.codes_files) do 189 ktrace_session:add_codes_file(file) 190 end 191end 192 193local ksancov_session = ksancov.Session.new(ktrace_session, flags) 194 195ksancov_session:add_event_handler(function (event) 196 event:print() 197 198 local function symbolicate(symbolicator, frame) 199 local symbol = symbolicator:symbolicate(frame) 200 201 if symbol then 202 print(("\t%s (in %s)"):format(symbol.name or "???", symbol.owner_name or "???")) 203 else 204 print(("\t0x%x (in ???)").format(frame)) 205 end 206 end 207 208 -- Symbolicate stacks 209 if event.ustack then 210 print('ustack:') 211 212 if flags.file then 213 -- When reading from a file, we can't use CoreSymbolication to symbolicate the stack frames as the processes are 214 -- not actually running, so use our ktrace session instead. 215 for _, frame in ipairs(event.ustack or {}) do 216 print((" %s"):format(ktrace_session:symbolicate_with_pid(event.pid, frame))) 217 end 218 else 219 local symbolicator = CoreSymbolication.Symbolicator.new(event.pid) 220 if symbolicator then 221 for _, frame in ipairs(event.ustack or {}) do 222 symbolicate(symbolicator, frame) 223 end 224 end 225 end 226 end 227 228 if event.kstack then 229 print('kstack:') 230 231 if flags.file then 232 -- When reading from a file, we can't use CoreSymbolication to symbolicate the stack frames as the processes are 233 -- not actually running, so use our ktrace session instead. 234 for _, frame in ipairs(event.kstack or {}) do 235 print((" %s"):format(ktrace_session:symbolicate_with_pid(event.pid, frame))) 236 end 237 else 238 local symbolicator = CoreSymbolication.Symbolicator.new(0) 239 if symbolicator then 240 for _, frame in ipairs(event.kstack or {}) do 241 symbolicate(symbolicator, frame) 242 end 243 end 244 end 245 end 246end) 247 248io.stdout:write("Started recording ...\n") 249local ok, err = ksancov_session:start() 250if not ok then 251 io.stderr:write("Failed to start session: ", err, "\n") 252 os.exit(1) 253end 254 255io.stdout:write("DONE.\n") 256 257