1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22/*
23 * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 */
26
27#pragma ident	"@(#)dt_isadep.c	1.14	06/02/22 SMI"
28
29#if defined(__arm__) || defined(__armv6__) || defined(__arm64__)
30
31#include <stdlib.h>
32#include <assert.h>
33#include <errno.h>
34#include <string.h>
35#include <libgen.h>
36
37#include <dt_impl.h>
38#include <dt_pid.h>
39
40#define SIGNEXTEND(x,v) ((((int) (x)) << (32-v)) >> (32-v))
41#define ALIGNADDR(x,v) (((x) >> v) << v)
42
43#define TYPE_SAME 0
44#define TYPE_TEXT 1
45#define TYPE_DATA 2
46
47int
48dt_pid_create_entry_probe(struct ps_prochandle *P, dtrace_hdl_t *dtp,
49			  fasttrap_probe_spec_t *ftp, const GElf_Sym *symp)
50{
51	char dmodel = Pstatus(P)->pr_dmodel;
52
53	ftp->ftps_probe_type = DTFTP_ENTRY;
54	ftp->ftps_pc = symp->st_value; // Keep st_value as uint64_t
55
56	if (dmodel == PR_MODEL_LP64) {
57		// 64 bit doesn't have arm vs thumb mode
58
59		ftp->ftps_arch_subinfo = 0;
60	} else {
61		if (symp->st_arch_subinfo == 0) {
62			dt_dprintf("No arm/thumb information for %s:%s, not instrumenting\n",ftp->ftps_mod,ftp->ftps_func);
63			return (1);
64		} else {
65			ftp->ftps_arch_subinfo = symp->st_arch_subinfo;
66		}
67	}
68
69	ftp->ftps_size = (size_t)symp->st_size;
70	ftp->ftps_noffs = 1;
71	ftp->ftps_offs[0] = 0;
72
73	if (ioctl(dtp->dt_ftfd, FASTTRAPIOC_MAKEPROBE, ftp) != 0) {
74		dt_dprintf("fasttrap probe creation ioctl failed: %s\n",
75		    strerror(errno));
76		return (dt_set_errno(dtp, errno));
77	}
78
79	return (1);
80}
81
82/*ARGSUSED*/
83static int
84dt_pid_create_return_probe32(struct ps_prochandle *P, dtrace_hdl_t *dtp,
85			   fasttrap_probe_spec_t *ftp, const GElf_Sym *symp, uint64_t *stret)
86{
87	/*
88	 * This function best read with the ARM architecture reference handy.
89	 */
90
91	uint8_t *text;
92
93	/*
94	 * When all the pc relative data is at the end of a function, there are no problems. But
95	 * after inlining, it's possible to have constants mixed in with the code. Create a table
96	 * to deal with this. It will be the same size as the instructions. When we instrument
97	 * instructions, we will have two modes: text and data. For our table, we will have three
98	 * values: TYPE_SAME - continue in previous mode; TYPE_TEXT - switch to text; TYPE_DATA -
99	 * switch to data.
100	 *
101	 * When we see a pc-relative load, we assume that is the beginning of a data section. When
102	 * we see a branch, we assume that is the beginning of more text.
103	 */
104	uint8_t *constants;
105
106	ftp->ftps_probe_type = DTFTP_RETURN;
107	ftp->ftps_pc = symp->st_value;
108
109	if (symp->st_arch_subinfo == 0) {
110		dt_dprintf("No arm/thumb information for %s:%s, not instrumenting\n",ftp->ftps_mod,ftp->ftps_func);
111		return (1);
112	} else {
113		ftp->ftps_arch_subinfo = symp->st_arch_subinfo;
114	}
115
116	ftp->ftps_size = (size_t)symp->st_size;
117	ftp->ftps_noffs = 0;
118
119	/*
120	 * We allocate a few extra bytes at the end so we don't have to check
121	 * for overrunning the buffer.
122	 */
123	if ((text = calloc(1, symp->st_size + 4)) == NULL) {
124		dt_dprintf("mr sparkle: malloc() failed\n");
125		return (DT_PROC_ERR);
126	}
127
128	if ((constants = calloc(1, symp->st_size + 4)) == NULL) {
129		dt_dprintf("mr sparkle: malloc() failed\n");
130		free(text);
131		return (DT_PROC_ERR);
132	}
133
134	/*
135	 * We run into all sorts of problems with the ldr instruction offset calculating, if
136	 * the original function starts on a halfword. The easiest workaround is to start our
137	 * base so that we start on a halfword too. The base address will need to be changed
138	 * back before we free the buffer. This problem only occurs in Thumb mode.
139	 */
140	if ((ftp->ftps_arch_subinfo == 2) && (symp->st_value & 2))
141		text = text + 2;
142
143	if (Pread(P, text, symp->st_size, symp->st_value) != symp->st_size) {
144		dt_dprintf("mr sparkle: Pread() failed\n");
145		free(text);
146		free(constants);
147		return (DT_PROC_ERR);
148	}
149
150	/* Approved methods of returning from a function and their valid encodings:
151	 * (A = ARM, T = Thumb 16 bit, T2 = Thumb 32 bit)
152	 *   ldr pc, [...] (A, T2)
153	 *   mov pc, reg (A, T)
154	 *   mov pc, immed (A)
155	 *   bx reg (A, T)
156	 *   b address (A, T, T2)
157	 *   ldmfd sp!, [ ... pc ] (A, T2)
158	 *   pop { ... pc } (T)
159	 *
160	 * We also first check for ldr reg, [pc ...] since that is a pc relative load, and a pc relative
161	 * load will tell us where any extra data is stored (after the end of a function).
162	 */
163
164	if (ftp->ftps_arch_subinfo == 1) {
165		/* Arm mode */
166		uint32_t* limit = (uint32_t*) (text + ftp->ftps_size);
167		uint32_t* inst = (uint32_t*) text;
168		ulong_t offset;
169		int mode = TYPE_TEXT;
170
171		while (inst < limit) {
172			offset = (ulong_t) inst - (ulong_t) text;
173			if ((mode == TYPE_DATA && constants[offset] == TYPE_SAME) || constants[offset] == TYPE_DATA) {
174				inst++;
175				mode = TYPE_DATA;
176				continue;
177			}
178			if (constants[offset] == TYPE_TEXT)
179				mode = TYPE_TEXT;
180
181			/*
182			 * All of the instructions we instrument are conditional, so if the instruction is
183			 * unconditional, skip immediately to the next one.
184			 */
185			if ((*inst & 0xF0000000) == 0xF0000000) {
186				inst++;
187				continue;
188			}
189
190			if ((*inst & 0x0F7F0000) == 0x051F0000) {
191				/* ldr reg, [pc, ...] */
192				uint32_t displacement = *inst & 0xFFF;
193				ulong_t target = ((uint32_t) inst) + 8;
194				if (*inst & (1 << 23)) {
195					target += displacement;
196				} else {
197					target -= displacement;
198				}
199				if (((ulong_t) inst) < target && target < ((ulong_t) limit))
200					constants[(ulong_t) target - (ulong_t) text] = TYPE_DATA;
201			}
202
203			if ((*inst & 0x0E50F000) == 0x0410F000) {
204				/* ldr pc, [...] */
205				/* We are using a jump table to do a jump. This could be equivalent to
206				 * either a branch or a branch and link. We only want to instrument
207				 * the first case. To identify a link, we will check the previous
208				 * instruction to see if it's a mov lr, pc. This is a little fragile,
209				 * but it will catch most cases.
210				 */
211				if (inst > text) {
212					/* It's not the first instruction in the function */
213					uint32_t prevInst = *(inst-1);
214
215					/* Also check to see if the condition codes are different */
216					if ((prevInst & 0xF0000000) != (*inst & 0xF0000000) ||
217					    (prevInst & 0x0FFFFFFF) != 0x01A0E00F)
218						goto is_ret_arm;
219				} else {
220					goto is_ret_arm;
221				}
222			} else if ((*inst & 0x0FFFFFF0) == 0x01A0F000) {
223				/* mov pc, reg */
224				goto is_ret_arm;
225			} else if ((*inst & 0x0FFFF000) == 0x03A0F000) {
226				/* mov pc, immed */
227				goto is_ret_arm;
228			} else if ((*inst & 0x0FFFFFF0) == 0x012FFF10) {
229				/* bx reg */
230				goto is_ret_arm;
231			} else if ((*inst & 0x0F000000) == 0x0A000000) {
232				/* branch */
233				ulong_t target = ((ulong_t) inst) + 8 + (SIGNEXTEND(*inst & 0x00FFFFFF,24) << 2);
234				if (((ulong_t) inst) < target && target < ((ulong_t) limit))
235					constants[(ulong_t) target - (ulong_t) text] = TYPE_TEXT;
236				if (target < text || limit <= target)
237					goto is_ret_arm;
238			} else if ((*inst & 0x0FFF8000) == 0x08BD8000) {
239				/* ldmfd sp!, { pc ... } */
240				goto is_ret_arm;
241			}
242
243			inst++;
244			continue;
245
246is_ret_arm:
247			offset = (ulong_t) inst - (ulong_t) text;
248			dt_dprintf("return at offset %lx Arm\n", offset);
249			ftp->ftps_offs[ftp->ftps_noffs++] = offset;
250			inst++;
251		}
252	} else if (ftp->ftps_arch_subinfo == 2) {
253		/* Thumb mode */
254		uint16_t* limit = (uint16_t*) (text + ftp->ftps_size);
255		uint16_t* inst = (uint16_t*) text;
256		ulong_t offset;
257		int mode = TYPE_TEXT;
258
259		while (inst < limit) {
260			offset = (ulong_t) inst - (ulong_t) text;
261
262			if ((mode == TYPE_DATA && constants[offset] == TYPE_SAME) || constants[offset] == TYPE_DATA) {
263				/* Data is always one full word */
264				inst += 2;
265				mode = TYPE_DATA;
266				continue;
267			}
268			if (constants[offset] == TYPE_TEXT)
269				mode = TYPE_TEXT;
270
271			if (((*inst >> 11) & 0x1F) > 0x1C) {
272				/* Four byte thumb instruction */
273				uint16_t* inst2 = inst+1;
274
275				if ((*inst & 0xFF7F) == 0xF85F) {
276					/* PC relative load */
277					uint32_t displacement = *inst2 & 0xFFF;
278					ulong_t target = ALIGNADDR(((uint32_t) inst)+4, 2);
279					if (*inst & (1 << 7)) {
280						target += displacement;
281					} else {
282						target -= displacement;
283					}
284					if (((ulong_t) inst) < target && target < ((ulong_t) limit))
285						constants[(ulong_t) target - (ulong_t) text] = TYPE_DATA;
286				} else if ((*inst & 0xFF3F) == 0xED1F && (*inst2 & 0x0E00) == 0x0A00) {
287					/* PC relative vload */
288					uint32_t displacement = (*inst2 * 0xFF) << 2;
289					ulong_t target = ALIGNADDR(((uint32_t) inst)+4, 2);
290					if (*inst & (1 << 7)) {
291						target += displacement;
292					} else {
293						target -= displacement;
294					}
295					if (((ulong_t) inst) < target && target < ((ulong_t) limit))
296						constants[(ulong_t) target - (ulong_t) text] = TYPE_DATA;
297				}
298
299				if ((*inst & 0xF800) == 0xF000 && (*inst2 & 0xD000) == 0x8000) {
300					int cond = (*inst >> 6) & 0xF;
301
302					if (cond != 0xE && cond != 0xF) {
303						/* Conditional branch */
304						int S = (*inst >> 10) & 1, J1 = (*inst2 >> 13) & 1, J2 = (*inst2 >> 11) & 1;
305						ulong_t target = ((ulong_t) inst) + 4 + SIGNEXTEND(
306							(S << 20) | (J2 << 19) | (J1 << 18) |
307							((*inst & 0x003F) << 12) | ((*inst2 & 0x07FF) << 1),
308							21);
309						if (((ulong_t) inst) < target && target < ((ulong_t) limit))
310							constants[(ulong_t) target - (ulong_t) text] = TYPE_TEXT;
311						if (target < text || limit <= target)
312							goto is_ret_thumb2;
313					}
314				} else if ((*inst & 0xF800) == 0xF000 && (*inst2 & 0xD000) == 0x9000) {
315					/* Unconditional branch */
316					int S = (*inst >> 10) & 1, J1 = (*inst2 >> 13) & 1, J2 = (*inst2 >> 11) & 1;
317					int I1 = (J1 != S) ? 0 : 1, I2 = (J2 != S) ? 0 : 1;
318					ulong_t target = ((ulong_t) inst) + 4 + SIGNEXTEND(
319						(S << 24) | (I1 << 23) | (I2 << 22) |
320						((*inst & 0x3FF) << 12) | ((*inst2 & 0x7FF) << 1),
321						25);
322					if (((ulong_t) inst) < target && target < ((ulong_t) limit))
323						constants[(ulong_t) target - (ulong_t) text] = TYPE_TEXT;
324					if (target < text || limit <= target)
325						goto is_ret_thumb2;
326				} else if (*inst == 0xE8BD && (*inst2 & 0x8000) == 0x8000) {
327					/* ldm sp!, { pc ... } */
328					goto is_ret_thumb2;
329				} else if ((*inst & 0xFF70) == 0xF850 && (*inst2 & 0xF000) == 0xF000) {
330					/* ldr pc, [ ... ] */
331					goto is_ret_thumb2;
332				}
333
334				inst += 2;
335				continue;
336
337is_ret_thumb2:
338				offset = (ulong_t) inst - (ulong_t) text;
339				dt_dprintf("return at offset %lx Thumb32\n", offset);
340				ftp->ftps_offs[ftp->ftps_noffs++] = offset;
341				inst += 2;
342			} else {
343				/* Two byte thumb instruction */
344				if ((*inst & 0xF800) == 0x4800) {
345					/* PC relative load */
346					ulong_t target = ALIGNADDR(((uint32_t) inst) + ((*inst & 0xFF) * 4) + 4, 2);
347					if (((ulong_t) inst) < target && target < ((ulong_t) limit))
348						constants[(ulong_t) target - (ulong_t) text] = TYPE_DATA;
349				}
350
351				if ((*inst & 0xFF87) == 0x4687) {
352					/* mov pc, reg */
353					goto is_ret_thumb;
354				} else if ((*inst & 0xFF87) == 0x4700) {
355					/* bx reg */
356					goto is_ret_thumb;
357				} else if ((*inst & 0xF000) == 0xD000) {
358					int cond = (*inst >> 8) & 0xF;
359					if (cond != 0xE && cond != 0xF) {
360						/* Conditional branch */
361						ulong_t target = ((ulong_t) inst) + 4 + (SIGNEXTEND(*inst & 0xFF,8) << 1);
362						if (((ulong_t) inst) < target && target < ((ulong_t) limit))
363							constants[(ulong_t) target - (ulong_t) text] = TYPE_TEXT;
364						if (target < text || limit <= target)
365							goto is_ret_thumb;
366					}
367				} else if ((*inst & 0xF800) == 0xE000) {
368					/* Unconditional branch */
369					ulong_t target = ((ulong_t) inst) + 4 + (SIGNEXTEND(*inst & 0x7FF,11) << 1);
370					if (((ulong_t) inst) < target && target < ((ulong_t) limit))
371						constants[(ulong_t) target - (ulong_t) text] = TYPE_TEXT;
372					if (target < text || limit <= target)
373						goto is_ret_thumb;
374				} else if ((*inst & 0xFF00) == 0xBD00) {
375					/* pop { ..., pc} */
376					goto is_ret_thumb;
377				}
378
379				inst++;
380				continue;
381
382is_ret_thumb:
383				offset = (ulong_t) inst - (ulong_t) text;
384				dt_dprintf("return at offset %lx Thumb16\n", offset);
385				ftp->ftps_offs[ftp->ftps_noffs++] = offset;
386				inst++;
387			}
388		}
389	}
390
391	/* Reset the buffer base (see above comments) */
392	if ((ftp->ftps_arch_subinfo == 2) && (symp->st_value & 2))
393		text = text - 2;
394	free(text);
395	free(constants);
396	if (ftp->ftps_noffs > 0) {
397		if (ioctl(dtp->dt_ftfd, FASTTRAPIOC_MAKEPROBE, ftp) != 0) {
398			dt_dprintf("fasttrap probe creation ioctl failed: %s\n",
399				strerror(errno));
400			return (dt_set_errno(dtp, errno));
401		}
402	}
403
404	return (ftp->ftps_noffs);
405}
406
407/*ARGSUSED*/
408static int
409dt_pid_create_return_probe64(struct ps_prochandle *P, dtrace_hdl_t *dtp,
410			   fasttrap_probe_spec_t *ftp, const GElf_Sym *symp, uint64_t *stret)
411{
412	uint8_t *text;
413
414	ftp->ftps_probe_type = DTFTP_RETURN;
415	ftp->ftps_pc = symp->st_value;
416
417	ftp->ftps_arch_subinfo = 0;
418
419	ftp->ftps_size = (size_t)symp->st_size;
420	ftp->ftps_noffs = 0;
421
422	/*
423	 * We allocate a few extra bytes at the end so we don't have to check
424	 * for overrunning the buffer.
425	 */
426	if ((text = calloc(1, symp->st_size + 4)) == NULL) {
427		dt_dprintf("mr sparkle: malloc() failed\n");
428		return (DT_PROC_ERR);
429	}
430
431	if (Pread(P, text, symp->st_size, symp->st_value) != symp->st_size) {
432		dt_dprintf("mr sparkle: Pread() failed\n");
433		free(text);
434		return (DT_PROC_ERR);
435	}
436
437	uint32_t* limit = (uint32_t*) (text + ftp->ftps_size);
438	uint32_t* inst = (uint32_t*) text;
439
440	while (inst < limit) {
441		/* b imm26 */
442		if (((*inst & 0xfc000000) == 0x14000000)) {
443			/*
444			 * The offset is from the address of this instruction in the
445			 * range +/-128MB, and is encoded as "imm26" times 4.
446			 */
447			uint32_t target = ((uint32_t) inst) + 4 * SIGNEXTEND(*inst & ((1 << 26) - 1), 26);
448			if (target < (uint32_t) text || (uint32_t) limit <= target) {
449				goto is_ret;
450			}
451		}
452
453		/* ret x30 */
454		if (*inst == 0xd65f03c0) {
455			goto is_ret;
456		}
457
458		inst++;
459		continue;
460
461is_ret:
462		dt_dprintf("return at offset %lx Arm64\n", (ulong_t) inst - (ulong_t) text);
463		ftp->ftps_offs[ftp->ftps_noffs++] = (ulong_t) inst - (ulong_t) text;
464		inst++;
465	}
466
467	free(text);
468	if (ftp->ftps_noffs > 0) {
469		if (ioctl(dtp->dt_ftfd, FASTTRAPIOC_MAKEPROBE, ftp) != 0) {
470			dt_dprintf("fasttrap probe creation ioctl failed: %s\n",
471				strerror(errno));
472			return (dt_set_errno(dtp, errno));
473		}
474	}
475
476	return (ftp->ftps_noffs);
477}
478
479/*ARGSUSED*/
480int
481dt_pid_create_return_probe(struct ps_prochandle *P, dtrace_hdl_t *dtp,
482			   fasttrap_probe_spec_t *ftp, const GElf_Sym *symp, uint64_t *stret)
483{
484	char dmodel = Pstatus(P)->pr_dmodel;
485
486	if (dmodel == PR_MODEL_LP64) {
487		return dt_pid_create_return_probe64(P, dtp, ftp, symp, stret);
488	} else {
489		return dt_pid_create_return_probe32(P, dtp, ftp, symp, stret);
490	}
491}
492
493/*ARGSUSED*/
494int
495dt_pid_create_offset_probe(struct ps_prochandle *P, dtrace_hdl_t *dtp,
496			   fasttrap_probe_spec_t *ftp, const GElf_Sym *symp, ulong_t off)
497{
498	/* Not implemented */
499	return (1);
500}
501
502/*ARGSUSED*/
503int
504dt_pid_create_glob_offset_probes(struct ps_prochandle *P, dtrace_hdl_t *dtp,
505				 fasttrap_probe_spec_t *ftp, const GElf_Sym *symp, const char *pattern)
506{
507	/* Not implemented */
508	return (-1);
509}
510
511#endif // __arm__ || __armv6__ || __arm64__
512