aboutsummaryrefslogtreecommitdiff
path: root/sysdeps/linux-gnu/ia64/trace.c
blob: 842b7c0fde682e693f75e0d72576774b04d55015 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
#if HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdlib.h>
#include <stddef.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <sys/ptrace.h>
#include <string.h>
#include <asm/ptrace_offsets.h>
#include <asm/rse.h>

#include "main.h"

/* What we think of as a bundle, ptrace thinks of it as two unsigned
 * longs */
union bundle_t {
	/* An IA64 instruction bundle has a 5 bit header describing the
	 * type of bundle, then 3 41 bit instructions
	 */
	struct {
		struct {
			unsigned long template:5;
			unsigned long slot0:41;
			unsigned long bot_slot1:18;
		} word0;
		struct {
			unsigned long top_slot1:23;
			unsigned long slot2:41;
		} word1;
	} bitmap;
	unsigned long code[2];
};

union cfm_t {
	struct {
		unsigned long sof:7;
		unsigned long sol:7;
		unsigned long sor:4;
		unsigned long rrb_gr:7;
		unsigned long rrb_fr:7;
		unsigned long rrb_pr:6;
	} cfm;
	unsigned long value;
};

int
syscall_p(Process *proc, int status, int *sysnum) {
	if (WIFSTOPPED(status)
	    && WSTOPSIG(status) == (SIGTRAP | proc->tracesysgood)) {
		unsigned long slot =
		    (ptrace(PTRACE_PEEKUSER, proc->pid, PT_CR_IPSR, 0) >> 41) &
		    0x3;
		unsigned long ip =
		    ptrace(PTRACE_PEEKUSER, proc->pid, PT_CR_IIP, 0);

		/* r15 holds the system call number */
		unsigned long r15 =
		    ptrace(PTRACE_PEEKUSER, proc->pid, PT_R15, 0);
		unsigned long insn;

		union bundle_t bundle;

		/* On fault, the IP has moved forward to the next
		 * slot.  If that is zero, then the actual place we
		 * broke was in the previous bundle, so wind back the
		 * IP.
		 */
		if (slot == 0)
			ip = ip - 16;
		bundle.code[0] = ptrace(PTRACE_PEEKTEXT, proc->pid, ip, 0);
		bundle.code[1] = ptrace(PTRACE_PEEKTEXT, proc->pid, ip + 8, 0);

		unsigned long bot = 0UL | bundle.bitmap.word0.bot_slot1;
		unsigned long top = 0UL | bundle.bitmap.word1.top_slot1;

		/* handle the rollback, slot 0 is actually slot 2 of
		 * the previous instruction (see above) */
		switch (slot) {
		case 0:
			insn = bundle.bitmap.word1.slot2;
			break;
		case 1:
			insn = bundle.bitmap.word0.slot0;
			break;
		case 2:
			/* make sure we're shifting about longs */
			insn = 0UL | bot | (top << 18UL);
			break;
		default:
			printf("Ummm, can't find instruction slot?\n");
			exit(1);
		}

		/* We need to support both the older break instruction
		 * type syscalls, and the new epc type ones.
		 *
		 * Bit 20 of the break constant is encoded in the "i"
		 * bit (bit 36) of the instruction, hence you should
		 * see 0x1000000000.
		 *
		 *  An EPC call is just 0x1ffffffffff
		 */
		if (insn == 0x1000000000 || insn == 0x1ffffffffff) {
			*sysnum = r15;
			if (proc->callstack_depth > 0 &&
				proc->callstack[proc->callstack_depth - 1].is_syscall &&
				proc->callstack[proc->callstack_depth - 1].c_un.syscall == *sysnum) {
				return 2;
			}
			return 1;
		}
	}
	return 0;
}

/* Stolen from David Mosberger's utrace tool, which he released under
   the GPL
   (http://www.gelato.unsw.edu.au/archives/linux-ia64/0104/1405.html) */
static inline double
fpreg_to_double (struct ia64_fpreg *fp) {
  double result;

  asm ("ldf.fill %0=%1" : "=f"(result) : "m"(*fp));
  return result;
}

static long
gimme_long_arg(enum tof type, Process *proc, int arg_num) {
	union cfm_t cfm;
	unsigned long bsp;

	bsp = ptrace(PTRACE_PEEKUSER, proc->pid, PT_AR_BSP, 0);
	cfm.value = ptrace(PTRACE_PEEKUSER, proc->pid, PT_CFM, 0);

	if (arg_num == -1)	/* return value */
		return ptrace(PTRACE_PEEKUSER, proc->pid, PT_R8, 0);

	/* First 8 arguments are passed in registers on the register
	 * stack, the following arguments are passed on the stack
	 * after a 16 byte scratch area
	 *
	 * If the function has returned, the ia64 register window has
	 * been reverted to the caller's configuration. So although in
	 * the callee, the first parameter is in R32, in the caller
	 * the first parameter comes in the registers after the local
	 * registers (really, input parameters plus locals, but the
	 * hardware doesn't track the distinction.) So we have to add
	 * in the size of the local area (sol) to find the first
	 * parameter passed to the callee. */
	if (type == LT_TOF_FUNCTION || type == LT_TOF_FUNCTIONR) {
		if (arg_num < 8) {
                        if (type == LT_TOF_FUNCTIONR)
				arg_num += cfm.cfm.sol;

			return ptrace(PTRACE_PEEKDATA, proc->pid,
				      (long)ia64_rse_skip_regs((unsigned long *)bsp,
							       -cfm.cfm.sof + arg_num),
				      0);
		} else {
			unsigned long sp =
			    ptrace(PTRACE_PEEKUSER, proc->pid, PT_R12, 0) + 16;
			return ptrace(PTRACE_PEEKDATA, proc->pid,
				      sp + (8 * (arg_num - 8)));
		}
	}

	if (type == LT_TOF_SYSCALL || LT_TOF_SYSCALLR)
		return ptrace(PTRACE_PEEKDATA, proc->pid,
			      (long)ia64_rse_skip_regs((unsigned long *)bsp, arg_num),
			      0);

	/* error if we get here */
	fprintf(stderr, "gimme_arg called with wrong arguments\n");
	exit(1);
}

static long float_regs[8] = { PT_F8, PT_F9, PT_F10, PT_F11,
			      PT_F12, PT_F13, PT_F14, PT_F15 };
static double
gimme_float_arg(enum tof type, Process *proc, int arg_num) {
	union cfm_t cfm;
	unsigned long bsp;
	struct ia64_fpreg reg;

	if (arg_num == -1) {	/* return value */
		reg.u.bits[0] = ptrace(PTRACE_PEEKUSER, proc->pid,
				       PT_F8, 0);
		reg.u.bits[1] = ptrace(PTRACE_PEEKUSER, proc->pid,
				       PT_F8 + 0x8, 0);
		return fpreg_to_double(&reg);
	}

	bsp = ptrace(PTRACE_PEEKUSER, proc->pid, PT_AR_BSP, 0);
	cfm.value = ptrace(PTRACE_PEEKUSER, proc->pid, PT_CFM, 0);

	/* The first 8 arguments are passed in regular registers
	 * (counting from R32), unless they are floating point values
	 * (the case in question here). In that case, up to the first
	 * 8 regular registers are still "allocated" for each of the
	 * first 8 parameters, but if a parameter is floating point,
	 * then the register is left unset and the parameter is passed
	 * in the first available floating-point register, counting
	 * from F8.
	 *
	 * Take func(int a, float f, int b, double d), for example.
	 *    a - passed in R32
	 *    f - R33 left unset, value passed in F8
	 *    b - passed in R34
	 *    d - R35 left unset, value passed in F9
	 *
	 * ltrace handles this by counting floating point arguments
	 * while parsing declarations. The "arg_num" in this routine
	 * (which is only called for floating point values) really
	 * means which floating point parameter we're looking for,
	 * ignoring everything else.
	 *
	 * Following the first 8 arguments, the remaining arguments
	 * are passed on the stack after a 16 byte scratch area
	 */
	if (type == LT_TOF_FUNCTION || type == LT_TOF_FUNCTIONR) {
		if (arg_num < 8) {
			reg.u.bits[0] = ptrace(PTRACE_PEEKUSER, proc->pid,
					       float_regs[arg_num], 0);
			reg.u.bits[1] = ptrace(PTRACE_PEEKUSER, proc->pid,
					       float_regs[arg_num] + 0x8, 0);
			return fpreg_to_double(&reg);
		} else {
			unsigned long sp =
			    ptrace(PTRACE_PEEKUSER, proc->pid, PT_R12, 0) + 16;
			reg.u.bits[0] = ptrace(PTRACE_PEEKDATA, proc->pid,
					       sp + (8 * (arg_num - 8)));
			reg.u.bits[0] = ptrace(PTRACE_PEEKDATA, proc->pid,
					       sp + (8 * (arg_num - 8)) + 0x8);
			return fpreg_to_double(&reg);
		}
	}

	/* error if we get here */
	fprintf(stderr, "gimme_arg called with wrong arguments\n");
	exit(1);
}

long
gimme_arg(enum tof type, Process *proc, int arg_num, arg_type_info *info) {
	union {
		long l;
		float f;
		double d;
	} cvt;

	if (info->type == ARGTYPE_FLOAT)
		cvt.f = gimme_float_arg(type, proc, info->u.float_info.float_index);
	else if (info->type == ARGTYPE_DOUBLE)
		cvt.d = gimme_float_arg(type, proc, info->u.double_info.float_index);
	else
		cvt.l = gimme_long_arg(type, proc, arg_num);

	return cvt.l;
}

void
save_register_args(enum tof type, Process *proc) {
}

void
get_arch_dep(Process *proc) {
}