Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/tools/objtool/disas.c
49303 views
1
// SPDX-License-Identifier: GPL-2.0-or-later
2
/*
3
* Copyright (C) 2015-2017 Josh Poimboeuf <[email protected]>
4
*/
5
6
#define _GNU_SOURCE
7
#include <fnmatch.h>
8
9
#include <objtool/arch.h>
10
#include <objtool/check.h>
11
#include <objtool/disas.h>
12
#include <objtool/special.h>
13
#include <objtool/warn.h>
14
15
#include <bfd.h>
16
#include <linux/string.h>
17
#include <tools/dis-asm-compat.h>
18
19
/*
20
* Size of the buffer for storing the result of disassembling
21
* a single instruction.
22
*/
23
#define DISAS_RESULT_SIZE 1024
24
25
struct disas_context {
26
struct objtool_file *file;
27
struct instruction *insn;
28
bool alt_applied;
29
char result[DISAS_RESULT_SIZE];
30
disassembler_ftype disassembler;
31
struct disassemble_info info;
32
};
33
34
/*
35
* Maximum number of alternatives
36
*/
37
#define DISAS_ALT_MAX 5
38
39
/*
40
* Maximum number of instructions per alternative
41
*/
42
#define DISAS_ALT_INSN_MAX 50
43
44
/*
45
* Information to disassemble an alternative
46
*/
47
struct disas_alt {
48
struct instruction *orig_insn; /* original instruction */
49
struct alternative *alt; /* alternative or NULL if default code */
50
char *name; /* name for this alternative */
51
int width; /* formatting width */
52
struct {
53
char *str; /* instruction string */
54
int offset; /* instruction offset */
55
int nops; /* number of nops */
56
} insn[DISAS_ALT_INSN_MAX]; /* alternative instructions */
57
int insn_idx; /* index of the next instruction to print */
58
};
59
60
#define DALT_DEFAULT(dalt) (!(dalt)->alt)
61
#define DALT_INSN(dalt) (DALT_DEFAULT(dalt) ? (dalt)->orig_insn : (dalt)->alt->insn)
62
#define DALT_GROUP(dalt) (DALT_INSN(dalt)->alt_group)
63
#define DALT_ALTID(dalt) ((dalt)->orig_insn->offset)
64
65
#define ALT_FLAGS_SHIFT 16
66
#define ALT_FLAG_NOT (1 << 0)
67
#define ALT_FLAG_DIRECT_CALL (1 << 1)
68
#define ALT_FEATURE_MASK ((1 << ALT_FLAGS_SHIFT) - 1)
69
70
static int alt_feature(unsigned int ft_flags)
71
{
72
return (ft_flags & ALT_FEATURE_MASK);
73
}
74
75
static int alt_flags(unsigned int ft_flags)
76
{
77
return (ft_flags >> ALT_FLAGS_SHIFT);
78
}
79
80
/*
81
* Wrapper around asprintf() to allocate and format a string.
82
* Return the allocated string or NULL on error.
83
*/
84
static char *strfmt(const char *fmt, ...)
85
{
86
va_list ap;
87
char *str;
88
int rv;
89
90
va_start(ap, fmt);
91
rv = vasprintf(&str, fmt, ap);
92
va_end(ap);
93
94
return rv == -1 ? NULL : str;
95
}
96
97
static int sprint_name(char *str, const char *name, unsigned long offset)
98
{
99
int len;
100
101
if (offset)
102
len = sprintf(str, "%s+0x%lx", name, offset);
103
else
104
len = sprintf(str, "%s", name);
105
106
return len;
107
}
108
109
#define DINFO_FPRINTF(dinfo, ...) \
110
((*(dinfo)->fprintf_func)((dinfo)->stream, __VA_ARGS__))
111
#define bfd_vma_fmt \
112
__builtin_choose_expr(sizeof(bfd_vma) == sizeof(unsigned long), "%#lx <%s>", "%#llx <%s>")
113
114
static int disas_result_fprintf(struct disas_context *dctx,
115
const char *fmt, va_list ap)
116
{
117
char *buf = dctx->result;
118
int avail, len;
119
120
len = strlen(buf);
121
if (len >= DISAS_RESULT_SIZE - 1) {
122
WARN_FUNC(dctx->insn->sec, dctx->insn->offset,
123
"disassembly buffer is full");
124
return -1;
125
}
126
avail = DISAS_RESULT_SIZE - len;
127
128
len = vsnprintf(buf + len, avail, fmt, ap);
129
if (len < 0 || len >= avail) {
130
WARN_FUNC(dctx->insn->sec, dctx->insn->offset,
131
"disassembly buffer is truncated");
132
return -1;
133
}
134
135
return 0;
136
}
137
138
static int disas_fprintf(void *stream, const char *fmt, ...)
139
{
140
va_list arg;
141
int rv;
142
143
va_start(arg, fmt);
144
rv = disas_result_fprintf(stream, fmt, arg);
145
va_end(arg);
146
147
return rv;
148
}
149
150
/*
151
* For init_disassemble_info_compat().
152
*/
153
static int disas_fprintf_styled(void *stream,
154
enum disassembler_style style,
155
const char *fmt, ...)
156
{
157
va_list arg;
158
int rv;
159
160
va_start(arg, fmt);
161
rv = disas_result_fprintf(stream, fmt, arg);
162
va_end(arg);
163
164
return rv;
165
}
166
167
static void disas_print_addr_sym(struct section *sec, struct symbol *sym,
168
bfd_vma addr, struct disassemble_info *dinfo)
169
{
170
char symstr[1024];
171
char *str;
172
173
if (sym) {
174
sprint_name(symstr, sym->name, addr - sym->offset);
175
DINFO_FPRINTF(dinfo, bfd_vma_fmt, addr, symstr);
176
} else {
177
str = offstr(sec, addr);
178
DINFO_FPRINTF(dinfo, bfd_vma_fmt, addr, str);
179
free(str);
180
}
181
}
182
183
static bool disas_print_addr_alt(bfd_vma addr, struct disassemble_info *dinfo)
184
{
185
struct disas_context *dctx = dinfo->application_data;
186
struct instruction *orig_first_insn;
187
struct alt_group *alt_group;
188
unsigned long offset;
189
struct symbol *sym;
190
191
/*
192
* Check if we are processing an alternative at the original
193
* instruction address (i.e. if alt_applied is true) and if
194
* we are referencing an address inside the alternative.
195
*
196
* For example, this happens if there is a branch inside an
197
* alternative. In that case, the address should be updated
198
* to a reference inside the original instruction flow.
199
*/
200
if (!dctx->alt_applied)
201
return false;
202
203
alt_group = dctx->insn->alt_group;
204
if (!alt_group || !alt_group->orig_group ||
205
addr < alt_group->first_insn->offset ||
206
addr > alt_group->last_insn->offset)
207
return false;
208
209
orig_first_insn = alt_group->orig_group->first_insn;
210
offset = addr - alt_group->first_insn->offset;
211
212
addr = orig_first_insn->offset + offset;
213
sym = orig_first_insn->sym;
214
215
disas_print_addr_sym(orig_first_insn->sec, sym, addr, dinfo);
216
217
return true;
218
}
219
220
static void disas_print_addr_noreloc(bfd_vma addr,
221
struct disassemble_info *dinfo)
222
{
223
struct disas_context *dctx = dinfo->application_data;
224
struct instruction *insn = dctx->insn;
225
struct symbol *sym = NULL;
226
227
if (disas_print_addr_alt(addr, dinfo))
228
return;
229
230
if (insn->sym && addr >= insn->sym->offset &&
231
addr < insn->sym->offset + insn->sym->len) {
232
sym = insn->sym;
233
}
234
235
disas_print_addr_sym(insn->sec, sym, addr, dinfo);
236
}
237
238
static void disas_print_addr_reloc(bfd_vma addr, struct disassemble_info *dinfo)
239
{
240
struct disas_context *dctx = dinfo->application_data;
241
struct instruction *insn = dctx->insn;
242
unsigned long offset;
243
struct reloc *reloc;
244
char symstr[1024];
245
char *str;
246
247
reloc = find_reloc_by_dest_range(dctx->file->elf, insn->sec,
248
insn->offset, insn->len);
249
if (!reloc) {
250
/*
251
* There is no relocation for this instruction although
252
* the address to resolve points to the next instruction.
253
* So this is an effective reference to the next IP, for
254
* example: "lea 0x0(%rip),%rdi". The kernel can reference
255
* the next IP with _THIS_IP_ macro.
256
*/
257
DINFO_FPRINTF(dinfo, bfd_vma_fmt, addr, "_THIS_IP_");
258
return;
259
}
260
261
offset = arch_insn_adjusted_addend(insn, reloc);
262
263
/*
264
* If the relocation symbol is a section name (for example ".bss")
265
* then we try to further resolve the name.
266
*/
267
if (reloc->sym->type == STT_SECTION) {
268
str = offstr(reloc->sym->sec, reloc->sym->offset + offset);
269
DINFO_FPRINTF(dinfo, bfd_vma_fmt, addr, str);
270
free(str);
271
} else {
272
sprint_name(symstr, reloc->sym->name, offset);
273
DINFO_FPRINTF(dinfo, bfd_vma_fmt, addr, symstr);
274
}
275
}
276
277
/*
278
* Resolve an address into a "<symbol>+<offset>" string.
279
*/
280
static void disas_print_address(bfd_vma addr, struct disassemble_info *dinfo)
281
{
282
struct disas_context *dctx = dinfo->application_data;
283
struct instruction *insn = dctx->insn;
284
struct instruction *jump_dest;
285
struct symbol *sym;
286
bool is_reloc;
287
288
/*
289
* If the instruction is a call/jump and it references a
290
* destination then this is likely the address we are looking
291
* up. So check it first.
292
*/
293
jump_dest = insn->jump_dest;
294
if (jump_dest && jump_dest->sym && jump_dest->offset == addr) {
295
if (!disas_print_addr_alt(addr, dinfo))
296
disas_print_addr_sym(jump_dest->sec, jump_dest->sym,
297
addr, dinfo);
298
return;
299
}
300
301
/*
302
* If the address points to the next instruction then there is
303
* probably a relocation. It can be a false positive when the
304
* current instruction is referencing the address of the next
305
* instruction. This particular case will be handled in
306
* disas_print_addr_reloc().
307
*/
308
is_reloc = (addr == insn->offset + insn->len);
309
310
/*
311
* The call destination offset can be the address we are looking
312
* up, or 0 if there is a relocation.
313
*/
314
sym = insn_call_dest(insn);
315
if (sym && (sym->offset == addr || (sym->offset == 0 && is_reloc))) {
316
DINFO_FPRINTF(dinfo, bfd_vma_fmt, addr, sym->name);
317
return;
318
}
319
320
if (!is_reloc)
321
disas_print_addr_noreloc(addr, dinfo);
322
else
323
disas_print_addr_reloc(addr, dinfo);
324
}
325
326
/*
327
* Initialize disassemble info arch, mach (32 or 64-bit) and options.
328
*/
329
int disas_info_init(struct disassemble_info *dinfo,
330
int arch, int mach32, int mach64,
331
const char *options)
332
{
333
struct disas_context *dctx = dinfo->application_data;
334
struct objtool_file *file = dctx->file;
335
336
dinfo->arch = arch;
337
338
switch (file->elf->ehdr.e_ident[EI_CLASS]) {
339
case ELFCLASS32:
340
dinfo->mach = mach32;
341
break;
342
case ELFCLASS64:
343
dinfo->mach = mach64;
344
break;
345
default:
346
return -1;
347
}
348
349
dinfo->disassembler_options = options;
350
351
return 0;
352
}
353
354
struct disas_context *disas_context_create(struct objtool_file *file)
355
{
356
struct disas_context *dctx;
357
struct disassemble_info *dinfo;
358
int err;
359
360
dctx = malloc(sizeof(*dctx));
361
if (!dctx) {
362
WARN("failed to allocate disassembly context");
363
return NULL;
364
}
365
366
dctx->file = file;
367
dinfo = &dctx->info;
368
369
init_disassemble_info_compat(dinfo, dctx,
370
disas_fprintf, disas_fprintf_styled);
371
372
dinfo->read_memory_func = buffer_read_memory;
373
dinfo->print_address_func = disas_print_address;
374
dinfo->application_data = dctx;
375
376
/*
377
* bfd_openr() is not used to avoid doing ELF data processing
378
* and caching that has already being done. Here, we just need
379
* to identify the target file so we call an arch specific
380
* function to fill some disassemble info (arch, mach).
381
*/
382
383
dinfo->arch = bfd_arch_unknown;
384
dinfo->mach = 0;
385
386
err = arch_disas_info_init(dinfo);
387
if (err || dinfo->arch == bfd_arch_unknown || dinfo->mach == 0) {
388
WARN("failed to init disassembly arch");
389
goto error;
390
}
391
392
dinfo->endian = (file->elf->ehdr.e_ident[EI_DATA] == ELFDATA2MSB) ?
393
BFD_ENDIAN_BIG : BFD_ENDIAN_LITTLE;
394
395
disassemble_init_for_target(dinfo);
396
397
dctx->disassembler = disassembler(dinfo->arch,
398
dinfo->endian == BFD_ENDIAN_BIG,
399
dinfo->mach, NULL);
400
if (!dctx->disassembler) {
401
WARN("failed to create disassembler function");
402
goto error;
403
}
404
405
return dctx;
406
407
error:
408
free(dctx);
409
return NULL;
410
}
411
412
void disas_context_destroy(struct disas_context *dctx)
413
{
414
free(dctx);
415
}
416
417
char *disas_result(struct disas_context *dctx)
418
{
419
return dctx->result;
420
}
421
422
#define DISAS_INSN_OFFSET_SPACE 10
423
#define DISAS_INSN_SPACE 60
424
425
#define DISAS_PRINSN(dctx, insn, depth) \
426
disas_print_insn(stdout, dctx, insn, depth, "\n")
427
428
/*
429
* Print a message in the instruction flow. If sec is not NULL then the
430
* address at the section offset is printed in addition of the message,
431
* otherwise only the message is printed.
432
*/
433
static int disas_vprint(FILE *stream, struct section *sec, unsigned long offset,
434
int depth, const char *format, va_list ap)
435
{
436
const char *addr_str;
437
int i, n;
438
int len;
439
440
len = sym_name_max_len + DISAS_INSN_OFFSET_SPACE;
441
if (depth < 0) {
442
len += depth;
443
depth = 0;
444
}
445
446
n = 0;
447
448
if (sec) {
449
addr_str = offstr(sec, offset);
450
n += fprintf(stream, "%6lx: %-*s ", offset, len, addr_str);
451
free((char *)addr_str);
452
} else {
453
len += DISAS_INSN_OFFSET_SPACE + 1;
454
n += fprintf(stream, "%-*s", len, "");
455
}
456
457
/* print vertical bars to show the code flow */
458
for (i = 0; i < depth; i++)
459
n += fprintf(stream, "| ");
460
461
if (format)
462
n += vfprintf(stream, format, ap);
463
464
return n;
465
}
466
467
static int disas_print(FILE *stream, struct section *sec, unsigned long offset,
468
int depth, const char *format, ...)
469
{
470
va_list args;
471
int len;
472
473
va_start(args, format);
474
len = disas_vprint(stream, sec, offset, depth, format, args);
475
va_end(args);
476
477
return len;
478
}
479
480
/*
481
* Print a message in the instruction flow. If insn is not NULL then
482
* the instruction address is printed in addition of the message,
483
* otherwise only the message is printed. In all cases, the instruction
484
* itself is not printed.
485
*/
486
void disas_print_info(FILE *stream, struct instruction *insn, int depth,
487
const char *format, ...)
488
{
489
struct section *sec;
490
unsigned long off;
491
va_list args;
492
493
if (insn) {
494
sec = insn->sec;
495
off = insn->offset;
496
} else {
497
sec = NULL;
498
off = 0;
499
}
500
501
va_start(args, format);
502
disas_vprint(stream, sec, off, depth, format, args);
503
va_end(args);
504
}
505
506
/*
507
* Print an instruction address (offset and function), the instruction itself
508
* and an optional message.
509
*/
510
void disas_print_insn(FILE *stream, struct disas_context *dctx,
511
struct instruction *insn, int depth,
512
const char *format, ...)
513
{
514
char fake_nop_insn[32];
515
const char *insn_str;
516
bool fake_nop;
517
va_list args;
518
int len;
519
520
/*
521
* Alternative can insert a fake nop, sometimes with no
522
* associated section so nothing to disassemble.
523
*/
524
fake_nop = (!insn->sec && insn->type == INSN_NOP);
525
if (fake_nop) {
526
snprintf(fake_nop_insn, 32, "<fake nop> (%d bytes)", insn->len);
527
insn_str = fake_nop_insn;
528
} else {
529
disas_insn(dctx, insn);
530
insn_str = disas_result(dctx);
531
}
532
533
/* print the instruction */
534
len = (depth + 1) * 2 < DISAS_INSN_SPACE ? DISAS_INSN_SPACE - (depth+1) * 2 : 1;
535
disas_print_info(stream, insn, depth, "%-*s", len, insn_str);
536
537
/* print message if any */
538
if (!format)
539
return;
540
541
if (strcmp(format, "\n") == 0) {
542
fprintf(stream, "\n");
543
return;
544
}
545
546
fprintf(stream, " - ");
547
va_start(args, format);
548
vfprintf(stream, format, args);
549
va_end(args);
550
}
551
552
/*
553
* Disassemble a single instruction. Return the size of the instruction.
554
*
555
* If alt_applied is true then insn should be an instruction from of an
556
* alternative (i.e. insn->alt_group != NULL), and it is disassembled
557
* at the location of the original code it is replacing. When the
558
* instruction references any address inside the alternative then
559
* these references will be re-adjusted to replace the original code.
560
*/
561
static size_t disas_insn_common(struct disas_context *dctx,
562
struct instruction *insn,
563
bool alt_applied)
564
{
565
disassembler_ftype disasm = dctx->disassembler;
566
struct disassemble_info *dinfo = &dctx->info;
567
568
dctx->insn = insn;
569
dctx->alt_applied = alt_applied;
570
dctx->result[0] = '\0';
571
572
if (insn->type == INSN_NOP) {
573
DINFO_FPRINTF(dinfo, "nop%d", insn->len);
574
return insn->len;
575
}
576
577
/*
578
* Set the disassembler buffer to read data from the section
579
* containing the instruction to disassemble.
580
*/
581
dinfo->buffer = insn->sec->data->d_buf;
582
dinfo->buffer_vma = 0;
583
dinfo->buffer_length = insn->sec->sh.sh_size;
584
585
return disasm(insn->offset, &dctx->info);
586
}
587
588
size_t disas_insn(struct disas_context *dctx, struct instruction *insn)
589
{
590
return disas_insn_common(dctx, insn, false);
591
}
592
593
static size_t disas_insn_alt(struct disas_context *dctx,
594
struct instruction *insn)
595
{
596
return disas_insn_common(dctx, insn, true);
597
}
598
599
static struct instruction *next_insn_same_alt(struct objtool_file *file,
600
struct alt_group *alt_grp,
601
struct instruction *insn)
602
{
603
if (alt_grp->last_insn == insn || alt_grp->nop == insn)
604
return NULL;
605
606
return next_insn_same_sec(file, insn);
607
}
608
609
#define alt_for_each_insn(file, alt_grp, insn) \
610
for (insn = alt_grp->first_insn; \
611
insn; \
612
insn = next_insn_same_alt(file, alt_grp, insn))
613
614
/*
615
* Provide a name for the type of alternatives present at the
616
* specified instruction.
617
*
618
* An instruction can have alternatives with different types, for
619
* example alternative instructions and an exception table. In that
620
* case the name for the alternative instructions type is used.
621
*
622
* Return NULL if the instruction as no alternative.
623
*/
624
const char *disas_alt_type_name(struct instruction *insn)
625
{
626
struct alternative *alt;
627
const char *name;
628
629
name = NULL;
630
for (alt = insn->alts; alt; alt = alt->next) {
631
if (alt->type == ALT_TYPE_INSTRUCTIONS) {
632
name = "alternative";
633
break;
634
}
635
636
switch (alt->type) {
637
case ALT_TYPE_EX_TABLE:
638
name = "ex_table";
639
break;
640
case ALT_TYPE_JUMP_TABLE:
641
name = "jump_table";
642
break;
643
default:
644
name = "unknown";
645
break;
646
}
647
}
648
649
return name;
650
}
651
652
/*
653
* Provide a name for an alternative.
654
*/
655
char *disas_alt_name(struct alternative *alt)
656
{
657
char pfx[4] = { 0 };
658
char *str = NULL;
659
const char *name;
660
int feature;
661
int flags;
662
int num;
663
664
switch (alt->type) {
665
666
case ALT_TYPE_EX_TABLE:
667
str = strdup("EXCEPTION");
668
break;
669
670
case ALT_TYPE_JUMP_TABLE:
671
str = strdup("JUMP");
672
break;
673
674
case ALT_TYPE_INSTRUCTIONS:
675
/*
676
* This is a non-default group alternative. Create a name
677
* based on the feature and flags associated with this
678
* alternative. Use either the feature name (it is available)
679
* or the feature number. And add a prefix to show the flags
680
* used.
681
*
682
* Prefix flags characters:
683
*
684
* '!' alternative used when feature not enabled
685
* '+' direct call alternative
686
* '?' unknown flag
687
*/
688
689
if (!alt->insn->alt_group)
690
return NULL;
691
692
feature = alt->insn->alt_group->feature;
693
num = alt_feature(feature);
694
flags = alt_flags(feature);
695
str = pfx;
696
697
if (flags & ~(ALT_FLAG_NOT | ALT_FLAG_DIRECT_CALL))
698
*str++ = '?';
699
if (flags & ALT_FLAG_DIRECT_CALL)
700
*str++ = '+';
701
if (flags & ALT_FLAG_NOT)
702
*str++ = '!';
703
704
name = arch_cpu_feature_name(num);
705
if (!name)
706
str = strfmt("%sFEATURE 0x%X", pfx, num);
707
else
708
str = strfmt("%s%s", pfx, name);
709
710
break;
711
}
712
713
return str;
714
}
715
716
/*
717
* Initialize an alternative. The default alternative should be initialized
718
* with alt=NULL.
719
*/
720
static int disas_alt_init(struct disas_alt *dalt,
721
struct instruction *orig_insn,
722
struct alternative *alt)
723
{
724
dalt->orig_insn = orig_insn;
725
dalt->alt = alt;
726
dalt->insn_idx = 0;
727
dalt->name = alt ? disas_alt_name(alt) : strdup("DEFAULT");
728
if (!dalt->name)
729
return -1;
730
dalt->width = strlen(dalt->name);
731
732
return 0;
733
}
734
735
static int disas_alt_add_insn(struct disas_alt *dalt, int index, char *insn_str,
736
int offset, int nops)
737
{
738
int len;
739
740
if (index >= DISAS_ALT_INSN_MAX) {
741
WARN("Alternative %lx.%s has more instructions than supported",
742
DALT_ALTID(dalt), dalt->name);
743
return -1;
744
}
745
746
len = strlen(insn_str);
747
dalt->insn[index].str = insn_str;
748
dalt->insn[index].offset = offset;
749
dalt->insn[index].nops = nops;
750
if (len > dalt->width)
751
dalt->width = len;
752
753
return 0;
754
}
755
756
static int disas_alt_jump(struct disas_alt *dalt)
757
{
758
struct instruction *orig_insn;
759
struct instruction *dest_insn;
760
char suffix[2] = { 0 };
761
char *str;
762
int nops;
763
764
orig_insn = dalt->orig_insn;
765
dest_insn = dalt->alt->insn;
766
767
if (orig_insn->type == INSN_NOP) {
768
if (orig_insn->len == 5)
769
suffix[0] = 'q';
770
str = strfmt("jmp%-3s %lx <%s+0x%lx>", suffix,
771
dest_insn->offset, dest_insn->sym->name,
772
dest_insn->offset - dest_insn->sym->offset);
773
nops = 0;
774
} else {
775
str = strfmt("nop%d", orig_insn->len);
776
nops = orig_insn->len;
777
}
778
779
if (!str)
780
return -1;
781
782
disas_alt_add_insn(dalt, 0, str, 0, nops);
783
784
return 1;
785
}
786
787
/*
788
* Disassemble an exception table alternative.
789
*/
790
static int disas_alt_extable(struct disas_alt *dalt)
791
{
792
struct instruction *alt_insn;
793
char *str;
794
795
alt_insn = dalt->alt->insn;
796
str = strfmt("resume at 0x%lx <%s+0x%lx>",
797
alt_insn->offset, alt_insn->sym->name,
798
alt_insn->offset - alt_insn->sym->offset);
799
if (!str)
800
return -1;
801
802
disas_alt_add_insn(dalt, 0, str, 0, 0);
803
804
return 1;
805
}
806
807
/*
808
* Disassemble an alternative and store instructions in the disas_alt
809
* structure. Return the number of instructions in the alternative.
810
*/
811
static int disas_alt_group(struct disas_context *dctx, struct disas_alt *dalt)
812
{
813
struct objtool_file *file;
814
struct instruction *insn;
815
int offset;
816
char *str;
817
int count;
818
int nops;
819
int err;
820
821
file = dctx->file;
822
count = 0;
823
offset = 0;
824
nops = 0;
825
826
alt_for_each_insn(file, DALT_GROUP(dalt), insn) {
827
828
disas_insn_alt(dctx, insn);
829
str = strdup(disas_result(dctx));
830
if (!str)
831
return -1;
832
833
nops = insn->type == INSN_NOP ? insn->len : 0;
834
err = disas_alt_add_insn(dalt, count, str, offset, nops);
835
if (err)
836
break;
837
offset += insn->len;
838
count++;
839
}
840
841
return count;
842
}
843
844
/*
845
* Disassemble the default alternative.
846
*/
847
static int disas_alt_default(struct disas_context *dctx, struct disas_alt *dalt)
848
{
849
char *str;
850
int nops;
851
int err;
852
853
if (DALT_GROUP(dalt))
854
return disas_alt_group(dctx, dalt);
855
856
/*
857
* Default alternative with no alt_group: this is the default
858
* code associated with either a jump table or an exception
859
* table and no other instruction alternatives. In that case
860
* the default alternative is made of a single instruction.
861
*/
862
disas_insn(dctx, dalt->orig_insn);
863
str = strdup(disas_result(dctx));
864
if (!str)
865
return -1;
866
nops = dalt->orig_insn->type == INSN_NOP ? dalt->orig_insn->len : 0;
867
err = disas_alt_add_insn(dalt, 0, str, 0, nops);
868
if (err)
869
return -1;
870
871
return 1;
872
}
873
874
/*
875
* For each alternative, if there is an instruction at the specified
876
* offset then print this instruction, otherwise print a blank entry.
877
* The offset is an offset from the start of the alternative.
878
*
879
* Return the offset for the next instructions to print, or -1 if all
880
* instructions have been printed.
881
*/
882
static int disas_alt_print_insn(struct disas_alt *dalts, int alt_count,
883
int insn_count, int offset)
884
{
885
struct disas_alt *dalt;
886
int offset_next;
887
char *str;
888
int i, j;
889
890
offset_next = -1;
891
892
for (i = 0; i < alt_count; i++) {
893
dalt = &dalts[i];
894
j = dalt->insn_idx;
895
if (j == -1) {
896
printf("| %-*s ", dalt->width, "");
897
continue;
898
}
899
900
if (dalt->insn[j].offset == offset) {
901
str = dalt->insn[j].str;
902
printf("| %-*s ", dalt->width, str ?: "");
903
if (++j < insn_count) {
904
dalt->insn_idx = j;
905
} else {
906
dalt->insn_idx = -1;
907
continue;
908
}
909
} else {
910
printf("| %-*s ", dalt->width, "");
911
}
912
913
if (dalt->insn[j].offset > 0 &&
914
(offset_next == -1 ||
915
(dalt->insn[j].offset < offset_next)))
916
offset_next = dalt->insn[j].offset;
917
}
918
printf("\n");
919
920
return offset_next;
921
}
922
923
/*
924
* Print all alternatives side-by-side.
925
*/
926
static void disas_alt_print_wide(char *alt_name, struct disas_alt *dalts, int alt_count,
927
int insn_count)
928
{
929
struct instruction *orig_insn;
930
int offset_next;
931
int offset;
932
int i;
933
934
orig_insn = dalts[0].orig_insn;
935
936
/*
937
* Print an header with the name of each alternative.
938
*/
939
disas_print_info(stdout, orig_insn, -2, NULL);
940
941
if (strlen(alt_name) > dalts[0].width)
942
dalts[0].width = strlen(alt_name);
943
printf("| %-*s ", dalts[0].width, alt_name);
944
945
for (i = 1; i < alt_count; i++)
946
printf("| %-*s ", dalts[i].width, dalts[i].name);
947
948
printf("\n");
949
950
/*
951
* Print instructions for each alternative.
952
*/
953
offset_next = 0;
954
do {
955
offset = offset_next;
956
disas_print(stdout, orig_insn->sec, orig_insn->offset + offset,
957
-2, NULL);
958
offset_next = disas_alt_print_insn(dalts, alt_count, insn_count,
959
offset);
960
} while (offset_next > offset);
961
}
962
963
/*
964
* Print all alternatives one above the other.
965
*/
966
static void disas_alt_print_compact(char *alt_name, struct disas_alt *dalts,
967
int alt_count, int insn_count)
968
{
969
struct instruction *orig_insn;
970
int width;
971
int i, j;
972
int len;
973
974
orig_insn = dalts[0].orig_insn;
975
976
len = disas_print(stdout, orig_insn->sec, orig_insn->offset, 0, NULL);
977
printf("%s\n", alt_name);
978
979
/*
980
* If all alternatives have a single instruction then print each
981
* alternative on a single line. Otherwise, print alternatives
982
* one above the other with a clear separation.
983
*/
984
985
if (insn_count == 1) {
986
width = 0;
987
for (i = 0; i < alt_count; i++) {
988
if (dalts[i].width > width)
989
width = dalts[i].width;
990
}
991
992
for (i = 0; i < alt_count; i++) {
993
printf("%*s= %-*s (if %s)\n", len, "", width,
994
dalts[i].insn[0].str, dalts[i].name);
995
}
996
997
return;
998
}
999
1000
for (i = 0; i < alt_count; i++) {
1001
printf("%*s= %s\n", len, "", dalts[i].name);
1002
for (j = 0; j < insn_count; j++) {
1003
if (!dalts[i].insn[j].str)
1004
break;
1005
disas_print(stdout, orig_insn->sec,
1006
orig_insn->offset + dalts[i].insn[j].offset, 0,
1007
"| %s\n", dalts[i].insn[j].str);
1008
}
1009
printf("%*s|\n", len, "");
1010
}
1011
}
1012
1013
/*
1014
* Trim NOPs in alternatives. This replaces trailing NOPs in alternatives
1015
* with a single indication of the number of bytes covered with NOPs.
1016
*
1017
* Return the maximum numbers of instructions in all alternatives after
1018
* trailing NOPs have been trimmed.
1019
*/
1020
static int disas_alt_trim_nops(struct disas_alt *dalts, int alt_count,
1021
int insn_count)
1022
{
1023
struct disas_alt *dalt;
1024
int nops_count;
1025
const char *s;
1026
int offset;
1027
int count;
1028
int nops;
1029
int i, j;
1030
1031
count = 0;
1032
for (i = 0; i < alt_count; i++) {
1033
offset = 0;
1034
nops = 0;
1035
nops_count = 0;
1036
dalt = &dalts[i];
1037
for (j = insn_count - 1; j >= 0; j--) {
1038
if (!dalt->insn[j].str || !dalt->insn[j].nops)
1039
break;
1040
offset = dalt->insn[j].offset;
1041
free(dalt->insn[j].str);
1042
dalt->insn[j].offset = 0;
1043
dalt->insn[j].str = NULL;
1044
nops += dalt->insn[j].nops;
1045
nops_count++;
1046
}
1047
1048
/*
1049
* All trailing NOPs have been removed. If there was a single
1050
* NOP instruction then re-add it. If there was a block of
1051
* NOPs then indicate the number of bytes than the block
1052
* covers (nop*<number-of-bytes>).
1053
*/
1054
if (nops_count) {
1055
s = nops_count == 1 ? "" : "*";
1056
dalt->insn[j + 1].str = strfmt("nop%s%d", s, nops);
1057
dalt->insn[j + 1].offset = offset;
1058
dalt->insn[j + 1].nops = nops;
1059
j++;
1060
}
1061
1062
if (j > count)
1063
count = j;
1064
}
1065
1066
return count + 1;
1067
}
1068
1069
/*
1070
* Disassemble an alternative.
1071
*
1072
* Return the last instruction in the default alternative so that
1073
* disassembly can continue with the next instruction. Return NULL
1074
* on error.
1075
*/
1076
static void *disas_alt(struct disas_context *dctx,
1077
struct instruction *orig_insn)
1078
{
1079
struct disas_alt dalts[DISAS_ALT_MAX] = { 0 };
1080
struct instruction *last_insn = NULL;
1081
struct alternative *alt;
1082
struct disas_alt *dalt;
1083
int insn_count = 0;
1084
int alt_count = 0;
1085
char *alt_name;
1086
int count;
1087
int i, j;
1088
int err;
1089
1090
alt_name = strfmt("<%s.%lx>", disas_alt_type_name(orig_insn),
1091
orig_insn->offset);
1092
if (!alt_name) {
1093
WARN("Failed to define name for alternative at instruction 0x%lx",
1094
orig_insn->offset);
1095
goto done;
1096
}
1097
1098
/*
1099
* Initialize and disassemble the default alternative.
1100
*/
1101
err = disas_alt_init(&dalts[0], orig_insn, NULL);
1102
if (err) {
1103
WARN("%s: failed to initialize default alternative", alt_name);
1104
goto done;
1105
}
1106
1107
insn_count = disas_alt_default(dctx, &dalts[0]);
1108
if (insn_count < 0) {
1109
WARN("%s: failed to disassemble default alternative", alt_name);
1110
goto done;
1111
}
1112
1113
/*
1114
* Initialize and disassemble all other alternatives.
1115
*/
1116
i = 1;
1117
for (alt = orig_insn->alts; alt; alt = alt->next) {
1118
if (i >= DISAS_ALT_MAX) {
1119
WARN("%s has more alternatives than supported", alt_name);
1120
break;
1121
}
1122
1123
dalt = &dalts[i];
1124
err = disas_alt_init(dalt, orig_insn, alt);
1125
if (err) {
1126
WARN("%s: failed to disassemble alternative", alt_name);
1127
goto done;
1128
}
1129
1130
count = -1;
1131
switch (dalt->alt->type) {
1132
case ALT_TYPE_INSTRUCTIONS:
1133
count = disas_alt_group(dctx, dalt);
1134
break;
1135
case ALT_TYPE_EX_TABLE:
1136
count = disas_alt_extable(dalt);
1137
break;
1138
case ALT_TYPE_JUMP_TABLE:
1139
count = disas_alt_jump(dalt);
1140
break;
1141
}
1142
if (count < 0) {
1143
WARN("%s: failed to disassemble alternative %s",
1144
alt_name, dalt->name);
1145
goto done;
1146
}
1147
1148
insn_count = count > insn_count ? count : insn_count;
1149
i++;
1150
}
1151
alt_count = i;
1152
1153
/*
1154
* Print default and non-default alternatives.
1155
*/
1156
1157
insn_count = disas_alt_trim_nops(dalts, alt_count, insn_count);
1158
1159
if (opts.wide)
1160
disas_alt_print_wide(alt_name, dalts, alt_count, insn_count);
1161
else
1162
disas_alt_print_compact(alt_name, dalts, alt_count, insn_count);
1163
1164
last_insn = orig_insn->alt_group ? orig_insn->alt_group->last_insn :
1165
orig_insn;
1166
1167
done:
1168
for (i = 0; i < alt_count; i++) {
1169
free(dalts[i].name);
1170
for (j = 0; j < insn_count; j++)
1171
free(dalts[i].insn[j].str);
1172
}
1173
1174
free(alt_name);
1175
1176
return last_insn;
1177
}
1178
1179
/*
1180
* Disassemble a function.
1181
*/
1182
static void disas_func(struct disas_context *dctx, struct symbol *func)
1183
{
1184
struct instruction *insn_start;
1185
struct instruction *insn;
1186
1187
printf("%s:\n", func->name);
1188
sym_for_each_insn(dctx->file, func, insn) {
1189
if (insn->alts) {
1190
insn_start = insn;
1191
insn = disas_alt(dctx, insn);
1192
if (insn)
1193
continue;
1194
/*
1195
* There was an error with disassembling
1196
* the alternative. Resume disassembling
1197
* at the current instruction, this will
1198
* disassemble the default alternative
1199
* only and continue with the code after
1200
* the alternative.
1201
*/
1202
insn = insn_start;
1203
}
1204
1205
DISAS_PRINSN(dctx, insn, 0);
1206
}
1207
printf("\n");
1208
}
1209
1210
/*
1211
* Disassemble all warned functions.
1212
*/
1213
void disas_warned_funcs(struct disas_context *dctx)
1214
{
1215
struct symbol *sym;
1216
1217
if (!dctx)
1218
return;
1219
1220
for_each_sym(dctx->file->elf, sym) {
1221
if (sym->warned)
1222
disas_func(dctx, sym);
1223
}
1224
}
1225
1226
void disas_funcs(struct disas_context *dctx)
1227
{
1228
bool disas_all = !strcmp(opts.disas, "*");
1229
struct section *sec;
1230
struct symbol *sym;
1231
1232
for_each_sec(dctx->file->elf, sec) {
1233
1234
if (!(sec->sh.sh_flags & SHF_EXECINSTR))
1235
continue;
1236
1237
sec_for_each_sym(sec, sym) {
1238
/*
1239
* If the function had a warning and the verbose
1240
* option is used then the function was already
1241
* disassemble.
1242
*/
1243
if (opts.verbose && sym->warned)
1244
continue;
1245
1246
if (disas_all || fnmatch(opts.disas, sym->name, 0) == 0)
1247
disas_func(dctx, sym);
1248
}
1249
}
1250
}
1251
1252