aboutsummaryrefslogtreecommitdiff
path: root/sysdeps/linux-gnu/aarch64/fetch.c
blob: 2744df068cc9ed39fa18b8f09ad91b9cf663b96f (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
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
/*
 * This file is part of ltrace.
 * Copyright (C) 2014 Petr Machata, Red Hat, Inc.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 */

#include <sys/ptrace.h>
#include <asm/ptrace.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#include "fetch.h"
#include "proc.h"
#include "type.h"
#include "value.h"

int aarch64_read_gregs(struct process *proc, struct user_pt_regs *regs);
int aarch64_read_fregs(struct process *proc, struct user_fpsimd_state *regs);


struct fetch_context
{
	struct user_pt_regs gregs;
	struct user_fpsimd_state fpregs;
	arch_addr_t nsaa;
	unsigned ngrn;
	unsigned nsrn;
	arch_addr_t x8;
};

static int
context_init(struct fetch_context *context, struct process *proc)
{
	if (aarch64_read_gregs(proc, &context->gregs) < 0
	    || aarch64_read_fregs(proc, &context->fpregs) < 0)
		return -1;

	context->ngrn = 0;
	context->nsrn = 0;
	/* XXX double cast */
	context->nsaa = (arch_addr_t) (uintptr_t) context->gregs.sp;
	context->x8 = 0;

	return 0;
}

struct fetch_context *
arch_fetch_arg_clone(struct process *proc, struct fetch_context *context)
{
	struct fetch_context *ret = malloc(sizeof(*ret));
	if (ret == NULL)
		return NULL;
	return memcpy(ret, context, sizeof(*ret));
}

static void
fetch_next_gpr(struct fetch_context *context, unsigned char *buf)
{
	uint64_t u = context->gregs.regs[context->ngrn++];
	memcpy(buf, &u, 8);
}

static int
fetch_gpr(struct fetch_context *context, struct value *value, size_t sz)
{
	if (sz < 8)
		sz = 8;

	unsigned char *buf = value_reserve(value, sz);
	if (buf == NULL)
		return -1;

	size_t i;
	for (i = 0; i < sz; i += 8)
		fetch_next_gpr(context, buf + i);

	return 0;
}

static void
fetch_next_sse(struct fetch_context *context, unsigned char *buf, size_t sz)
{
	__int128 u = context->fpregs.vregs[context->nsrn++];
	memcpy(buf, &u, sz);
}

static int
fetch_sse(struct fetch_context *context, struct value *value, size_t sz)
{
	unsigned char *buf = value_reserve(value, sz);
	if (buf == NULL)
		return -1;

	fetch_next_sse(context, buf, sz);
	return 0;
}

static int
fetch_hfa(struct fetch_context *context,
	  struct value *value, struct arg_type_info *hfa_t, size_t count)
{
	size_t sz = type_sizeof(value->inferior, hfa_t);
	unsigned char *buf = value_reserve(value, sz * count);
	if (buf == NULL)
		return -1;

	size_t i;
	for (i = 0; i < count; ++i) {
		fetch_next_sse(context, buf, sz);
		buf += sz;
	}
	return 0;
}

static int
fetch_stack(struct fetch_context *context, struct value *value,
	    size_t align, size_t sz)
{
	if (align < 8)
		align = 8;
	size_t amount = ((sz + align - 1) / align) * align;

	/* XXX double casts */
	uintptr_t sp = (uintptr_t) context->nsaa;
	sp = ((sp + align - 1) / align) * align;

	value_in_inferior(value, (arch_addr_t) sp);

	sp += amount;
	context->nsaa = (arch_addr_t) sp;

	return 0;
}

enum convert_method {
	CVT_ERR = -1,
	CVT_NOP = 0,
	CVT_BYREF,
};

enum fetch_method {
	FETCH_NOP,
	FETCH_STACK,
	FETCH_GPR,
	FETCH_SSE,
	FETCH_HFA,
};

struct fetch_script {
	enum convert_method c;
	enum fetch_method f;
	size_t sz;
	struct arg_type_info *hfa_t;
	size_t count;
};

static struct fetch_script
pass_arg(struct fetch_context const *context,
	 struct process *proc, struct arg_type_info *info)
{
	enum fetch_method cvt = CVT_NOP;

	size_t sz = type_sizeof(proc, info);
	if (sz == (size_t) -1)
		return (struct fetch_script) { CVT_ERR, FETCH_NOP, sz };

	switch (info->type) {
	case ARGTYPE_VOID:
		return (struct fetch_script) { cvt, FETCH_NOP, sz };

	case ARGTYPE_STRUCT:
	case ARGTYPE_ARRAY:;
		size_t count;
		struct arg_type_info *hfa_t = type_get_hfa_type(info, &count);
		if (hfa_t != NULL && count <= 4) {
			if (context->nsrn + count <= 8)
				return (struct fetch_script)
					{ cvt, FETCH_HFA, sz, hfa_t, count };
			return (struct fetch_script)
				{ cvt, FETCH_STACK, sz, hfa_t, count };
		}

		if (sz <= 16) {
			size_t count = sz / 8;
			if (context->ngrn + count <= 8)
				return (struct fetch_script)
					{ cvt, FETCH_GPR, sz };
		}

		cvt = CVT_BYREF;
		sz = 8;
		/* Fall through.  */

	case ARGTYPE_POINTER:
	case ARGTYPE_INT:
	case ARGTYPE_UINT:
	case ARGTYPE_LONG:
	case ARGTYPE_ULONG:
	case ARGTYPE_CHAR:
	case ARGTYPE_SHORT:
	case ARGTYPE_USHORT:
		if (context->ngrn < 8 && sz <= 8)
			return (struct fetch_script) { cvt, FETCH_GPR, sz };
		/* We don't support types wider than 8 bytes as of
		 * now.  */
		assert(sz <= 8);

		return (struct fetch_script) { cvt, FETCH_STACK, sz };

	case ARGTYPE_FLOAT:
	case ARGTYPE_DOUBLE:
		if (context->nsrn < 8) {
			/* ltrace doesn't support float128.  */
			assert(sz <= 8);
			return (struct fetch_script) { cvt, FETCH_SSE, sz };
		}

		return (struct fetch_script) { cvt, FETCH_STACK, sz };
	}

	assert(! "Failed to allocate argument.");
	abort();
}

static int
convert_arg(struct value *value, struct fetch_script how)
{
	switch (how.c) {
	case CVT_NOP:
		return 0;
	case CVT_BYREF:
		return value_pass_by_reference(value);
	case CVT_ERR:
		return -1;
	}

	assert(! "Don't know how to convert argument.");
	abort();
}

static int
fetch_arg(struct fetch_context *context,
	  struct process *proc, struct arg_type_info *info,
	  struct value *value, struct fetch_script how)
{
	if (convert_arg(value, how) < 0)
		return -1;

	switch (how.f) {
	case FETCH_NOP:
		return 0;

	case FETCH_STACK:
		if (how.hfa_t != NULL && how.count != 0 && how.count <= 8)
			context->nsrn = 8;
		return fetch_stack(context, value,
				   type_alignof(proc, info), how.sz);

	case FETCH_GPR:
		return fetch_gpr(context, value, how.sz);

	case FETCH_SSE:
		return fetch_sse(context, value, how.sz);

	case FETCH_HFA:
		return fetch_hfa(context, value, how.hfa_t, how.count);
	}

	assert(! "Don't know how to fetch argument.");
	abort();
}

struct fetch_context *
arch_fetch_arg_init(enum tof type, struct process *proc,
		    struct arg_type_info *ret_info)
{
	struct fetch_context *context = malloc(sizeof *context);
	if (context == NULL || context_init(context, proc) < 0) {
	fail:
		free(context);
		return NULL;
	}

	/* There's a provision in ARMv8 parameter passing convention
	 * for returning types that, if passed as first argument to a
	 * function, would be passed on stack.  For those types, x8
	 * contains an address where the return argument should be
	 * placed.  The callee doesn't need to preserve the value of
	 * x8, so we need to fetch it now.
	 *
	 * To my knowledge, there are currently no types where this
	 * holds, but the code is here, utterly untested.  */

	struct fetch_script how = pass_arg(context, proc, ret_info);
	if (how.c == CVT_ERR)
		goto fail;
	if (how.c == CVT_NOP && how.f == FETCH_STACK) {
		/* XXX double cast.  */
		context->x8 = (arch_addr_t) (uintptr_t) context->gregs.regs[8];
		/* See the comment above about the assert.  */
		assert(! "Unexpected: first argument passed on stack.");
		abort();
	}

	return context;
}

int
arch_fetch_arg_next(struct fetch_context *context, enum tof type,
		    struct process *proc, struct arg_type_info *info,
		    struct value *value)
{
	return fetch_arg(context, proc, info, value,
			 pass_arg(context, proc, info));
}

int
arch_fetch_retval(struct fetch_context *context, enum tof type,
		  struct process *proc, struct arg_type_info *info,
		  struct value *value)
{
	if (context->x8 != 0) {
		value_in_inferior(value, context->x8);
		return 0;
	}

	if (context_init(context, proc) < 0)
		return -1;

	return fetch_arg(context, proc, info, value,
			 pass_arg(context, proc, info));
}

void
arch_fetch_arg_done(struct fetch_context *context)
{
	if (context != NULL)
		free(context);
}

size_t
arch_type_sizeof(struct process *proc, struct arg_type_info *arg)
{
	return (size_t) -2;
}

size_t
arch_type_alignof(struct process *proc, struct arg_type_info *arg)
{
	return (size_t) -2;
}