xref: /xnu-11215.41.3/san/tools/kstksz (revision 33de042d024d46de5ff4e89f2471de6608e37fa4)
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