1 /*
2  * This file is based on a patch submitted by Mark Wielaard <mjw@redhat.com>
3  * to ltrace project:
4  * https://anonscm.debian.org/cgit/collab-maint/ltrace.git/commit/?id=dfefa9f057857735a073ea655f5cb34351032c8e
5  *
6  * It was re-licensed for strace by the original author:
7  * https://lists.strace.io/pipermail/strace-devel/2018-March/008063.html
8  *
9  * Copyright (c) 2014-2018 Mark Wielaard <mjw@redhat.com>
10  * Copyright (c) 2018 Masatake YAMATO <yamato@redhat.com>
11  * Copyright (c) 2018 The strace developers.
12  * All rights reserved.
13  *
14  * Redistribution and use in source and binary forms, with or without
15  * modification, are permitted provided that the following conditions
16  * are met:
17  * 1. Redistributions of source code must retain the above copyright
18  *    notice, this list of conditions and the following disclaimer.
19  * 2. Redistributions in binary form must reproduce the above copyright
20  *    notice, this list of conditions and the following disclaimer in the
21  *    documentation and/or other materials provided with the distribution.
22  * 3. The name of the author may not be used to endorse or promote products
23  *    derived from this software without specific prior written permission.
24  *
25  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
26  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
27  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
28  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
29  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
30  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
31  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
32  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
33  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
34  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35  */
36 
37 #include "defs.h"
38 #include "unwind.h"
39 #include "mmap_notify.h"
40 #include <elfutils/libdwfl.h>
41 
42 struct ctx {
43 	Dwfl *dwfl;
44 	unsigned int last_proc_updating;
45 };
46 
47 static unsigned int mapping_generation;
48 
49 static void
update_mapping_generation(struct tcb * tcp,void * unused)50 update_mapping_generation(struct tcb *tcp, void *unused)
51 {
52 	mapping_generation++;
53 }
54 
55 static void
init(void)56 init(void)
57 {
58 	mmap_notify_register_client(update_mapping_generation, NULL);
59 }
60 
61 static void *
tcb_init(struct tcb * tcp)62 tcb_init(struct tcb *tcp)
63 {
64 	static const Dwfl_Callbacks proc_callbacks = {
65 		.find_elf = dwfl_linux_proc_find_elf,
66 		.find_debuginfo = dwfl_standard_find_debuginfo
67 	};
68 
69 	Dwfl *dwfl = dwfl_begin(&proc_callbacks);
70 	if (dwfl == NULL) {
71 		error_msg("dwfl_begin: %s", dwfl_errmsg(-1));
72 		return NULL;
73 	}
74 
75 	int r = dwfl_linux_proc_attach(dwfl, tcp->pid, true);
76 	if (r) {
77 		const char *msg = NULL;
78 
79 		if (r < 0)
80 			msg = dwfl_errmsg(-1);
81 		else if (r > 0)
82 			msg = strerror(r);
83 
84 		error_msg("dwfl_linux_proc_attach returned an error"
85 			  " for process %d: %s", tcp->pid, msg);
86 		dwfl_end(dwfl);
87 		return NULL;
88 	}
89 
90 	struct ctx *ctx = xmalloc(sizeof(*ctx));
91 	ctx->dwfl = dwfl;
92 	ctx->last_proc_updating = 0;
93 	return ctx;
94 }
95 
96 static void
tcb_fin(struct tcb * tcp)97 tcb_fin(struct tcb *tcp)
98 {
99 	struct ctx *ctx = tcp->unwind_ctx;
100 	if (ctx) {
101 		dwfl_end(ctx->dwfl);
102 		free(ctx);
103 	}
104 }
105 
106 static void
flush_cache_maybe(struct tcb * tcp)107 flush_cache_maybe(struct tcb *tcp)
108 {
109 	struct ctx *ctx = tcp->unwind_ctx;
110 	if (!ctx)
111 		return;
112 
113 	if (ctx->last_proc_updating == mapping_generation)
114 		return;
115 
116 	int r = dwfl_linux_proc_report(ctx->dwfl, tcp->pid);
117 
118 	if (r < 0)
119 		error_msg("dwfl_linux_proc_report returned an error"
120 			  " for pid %d: %s", tcp->pid, dwfl_errmsg(-1));
121 	else if (r > 0)
122 		error_msg("dwfl_linux_proc_report returned an error"
123 			  " for pid %d", tcp->pid);
124 	else if (dwfl_report_end(ctx->dwfl, NULL, NULL) != 0)
125 		error_msg("dwfl_report_end returned an error"
126 			  " for pid %d: %s", tcp->pid, dwfl_errmsg(-1));
127 
128 	ctx->last_proc_updating = mapping_generation;
129 }
130 
131 struct frame_user_data {
132 	unwind_call_action_fn call_action;
133 	unwind_error_action_fn error_action;
134 	void *data;
135 	int stack_depth;
136 };
137 
138 static int
frame_callback(Dwfl_Frame * state,void * arg)139 frame_callback(Dwfl_Frame *state, void *arg)
140 {
141 	struct frame_user_data *user_data = arg;
142 	Dwarf_Addr pc;
143 	bool isactivation;
144 
145 	if (!dwfl_frame_pc(state, &pc, &isactivation)) {
146 		/* Propagate the error to the caller.  */
147 		return -1;
148 	}
149 
150 	if (!isactivation)
151 		pc--;
152 
153 	Dwfl *dwfl = dwfl_thread_dwfl(dwfl_frame_thread(state));
154 	Dwfl_Module *mod = dwfl_addrmodule(dwfl, pc);
155 	GElf_Off off = 0;
156 
157 	if (mod != NULL) {
158 		const char *modname = NULL;
159 		const char *symname = NULL;
160 		GElf_Sym sym;
161 		Dwarf_Addr true_offset = pc;
162 
163 		modname = dwfl_module_info(mod, NULL, NULL, NULL, NULL,
164 					   NULL, NULL, NULL);
165 		symname = dwfl_module_addrinfo(mod, pc, &off, &sym,
166 					       NULL, NULL, NULL);
167 		dwfl_module_relocate_address(mod, &true_offset);
168 		user_data->call_action(user_data->data, modname, symname,
169 				       off, true_offset);
170 	}
171 	/* Max number of frames to print reached? */
172 	if (user_data->stack_depth-- == 0)
173 		return DWARF_CB_ABORT;
174 
175 	return DWARF_CB_OK;
176 }
177 
178 static void
tcb_walk(struct tcb * tcp,unwind_call_action_fn call_action,unwind_error_action_fn error_action,void * data)179 tcb_walk(struct tcb *tcp,
180 	 unwind_call_action_fn call_action,
181 	 unwind_error_action_fn error_action,
182 	 void *data)
183 {
184 	struct ctx *ctx = tcp->unwind_ctx;
185 	if (!ctx)
186 		return;
187 
188 	struct frame_user_data user_data = {
189 		.call_action = call_action,
190 		.error_action = error_action,
191 		.data = data,
192 		.stack_depth = 256,
193 	};
194 
195 	flush_cache_maybe(tcp);
196 
197 	int r = dwfl_getthread_frames(ctx->dwfl, tcp->pid, frame_callback,
198 				      &user_data);
199 	if (r)
200 		error_action(data,
201 			     r < 0 ? dwfl_errmsg(-1) : "too many stack frames",
202 			     0);
203 }
204 
205 const struct unwind_unwinder_t unwinder = {
206 	.name = "libdw",
207 	.init = init,
208 	.tcb_init = tcb_init,
209 	.tcb_fin = tcb_fin,
210 	.tcb_walk = tcb_walk,
211 };
212