I am writing a tool which uses libbfd
and libopcodes
in x86-32 and x86-64 Linux to perform disassembly. The problem is that whilst I am able to get libopcodes to disassemble, I am unable to get any instruction information. For the purposes of demonstration, I have made a minimal example which reproduces my issue. The program should disassemble itself from entry point to the first RET
/RETQ
.
The code is a bit hacked up with globals and error checking has been omitted for brevity, etc. but should illustrate the issue clearly.
#include <bfd.h>
#include <dis-asm.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>
#include <libiberty.h>
/*
* Holds state for BFD and libopcodes.
*/
bfd * abfd = NULL;
disassemble_info dinfo = {0};
/*
* Temporary hack to signal when disassembling should stop.
*/
static bool stop_disassembling = FALSE;
/*
* Gets path to currently running executable.
*/
bool get_target_path(char * target_path, size_t size)
{
char * path;
ssize_t len;
pid_t pid = getpid();
sprintf(target_path, "/proc/%d/exe", (int)pid );
path = strdup(target_path);
len = readlink(path, target_path, size);
target_path[len] = '';
free(path);
return TRUE;
}
/*
* libopcodes appends spaces on the end of some instructions so for
* comparisons, we want to strip those first.
*/
void strip_tail(char * str, unsigned int size)
{
int i;
for(i = 0; i < size; i++) {
if(!isgraph(str[i])) {
str[i] = '';
break;
}
}
}
/*
* Checks whether the current instruction will cause the control flow to not
* proceed to the linearly subsequent instruction (e.g. ret, jmp, etc.)
*/
bool breaks_control_flow(char * str)
{
if(abfd->arch_info->bits_per_address == 64) {
if(strcmp(str, "retq") == 0) {
return TRUE;
}
} else {
if(strcmp(str, "ret") == 0) {
return TRUE;
}
}
return FALSE;
}
/*
* Used as a callback for libopcodes so we can do something useful with the
* disassembly. Currently this just outputs to stdout.
*/
int custom_fprintf(void * stream, const char * format, ...)
{
/* silly amount */
char str[128] = {0};
int rv;
va_list args;
va_start(args, format);
rv = vsnprintf(str, ARRAY_SIZE(str) - 1, format, args);
va_end(args);
puts(str);
strip_tail(str, ARRAY_SIZE(str));
if(breaks_control_flow(str)) {
puts("Stopped disassembly");
stop_disassembling = TRUE;
}
if(dinfo.insn_info_valid) {
switch(dinfo.insn_type) {
case dis_noninsn:
printf("not an instruction
");
break;
case dis_nonbranch:
printf("not a branch
");
break;
case dis_branch:
printf("is a branch
");
break;
case dis_condbranch:
printf("is a conditional branch
");
break;
case dis_jsr:
printf("jump to subroutine
");
break;
case dis_condjsr:
printf("conditional jump to subroutine
");
break;
case dis_dref:
printf("data reference in instruction
");
break;
case dis_dref2:
printf("two data references in instruction
");
break;
default:
printf("not enumerated
");
break;
}
} else {
printf("insn_info not valid
");
}
return rv;
}
/*
* Initialises libopcodes disassembler and returns an instance of it.
*/
disassembler_ftype init_disasm(bfd * abfd, disassemble_info * dinfo)
{
/* Override the stream the disassembler outputs to */
init_disassemble_info(dinfo, NULL, custom_fprintf);
dinfo->flavour = bfd_get_flavour(abfd);
dinfo->arch = bfd_get_arch(abfd);
dinfo->mach = bfd_get_mach(abfd);
dinfo->endian = abfd->xvec->byteorder;
disassemble_init_for_target(dinfo);
return disassembler(abfd);
}
/*
* Method of locating section from VMA taken from opdis.
*/
typedef struct {
bfd_vma vma;
asection * sec;
} BFD_VMA_SECTION;
/*
* Loads section and fills in dinfo accordingly. Since this function allocates
* memory in dinfo->buffer, callers need to call free once they are finished.
*/
bool load_section(bfd * abfd, disassemble_info * dinfo, asection * s)
{
int size = bfd_section_size(s->owner, s);
unsigned char * buf = xmalloc(size);
if(!bfd_get_section_contents(s->owner, s, buf, 0, size)) {
free(buf);
return FALSE;
}
dinfo->section = s;
dinfo->buffer = buf;
dinfo->buffer_length = size;
dinfo->buffer_vma = bfd_section_vma(s->owner, s);
printf("Allocated %d bytes for %s section
: 0x%lX", size, s->name,
dinfo->buffer_vma);
return TRUE;
}
/*
* Used to locate section for a vma.
*/
void vma_in_section(bfd * abfd, asection * s, void * data)
{
BFD_VMA_SECTION * req = data;
if(req && req->vma >= s->vma &&
req->vma < (s->vma + bfd_section_size(abfd, s)) ) {
req->sec = s;
}
}
/*
* Locate and load section containing vma.
*/
bool load_section_for_vma(bfd * abfd, disassemble_info * dinfo,
bfd_vma vma)
{
BFD_VMA_SECTION req = {vma, NULL};
bfd_map_over_sections(abfd, vma_in_section, &req);
if(!req.sec) {
return FALSE;
} else {
return load_section(abfd, dinfo, req.sec);
}
}
/*
* Start disassembling from entry point.
*/
bool disassemble_entry(bfd * abfd, disassemble_info * dinfo,
disassembler_ftype disassembler)
{
bfd_vma vma = bfd_get_start_address(abfd);
/* First locate and load the section containing the vma */
if(load_section_for_vma(abfd, dinfo, vma)) {
int size;
/* Keep disassembling until signalled otherwise or error */
while(true) {
dinfo->insn_info_valid = 0;
size = disassembler(vma, dinfo);
printf("Disassembled %d bytes at 0x%lX
", size, vma);
if(size == 0 || size == -1 || stop_disassembling) {
break;
}
vma += size;
}
free(dinfo->buffer);
return TRUE;
}
return FALSE;
}
int main(void)
{
char target_path[PATH_MAX] = {0};
bfd_init();
/* Get path for the running instance of this program */
get_target_path(target_path, ARRAY_SIZE(target_path));
abfd = bfd_openr(target_path, NULL);
if(abfd != NULL && bfd_check_format(abfd, bfd_object)) {
disassembler_ftype disassembler = init_disasm(abfd, &dinfo);
disassemble_entry(abfd, &dinfo, disassembler);
bfd_close(abfd);
}
return EXIT_SUCCESS;
}
This source can be built with the following makefile
. To perform a successful link, the binutils-dev
package needs to be installed on the local machine:
all:
gcc -Wall disasm.c -o disasm -lbfd -lopcodes
clean:
rm -f disasm
When run, the output is this:
Allocated 2216 bytes for .text section
: 0x400BF0xor
insn_info not valid
%ebp
insn_info not valid
,
insn_info not valid
%ebp
insn_info not valid
Disassembled 2 bytes at 0x400BF0
mov
insn_info not valid
%rdx
insn_info not valid
,
insn_info not valid
%r9
insn_info not valid
Disassembled 3 bytes at 0x400BF2
pop
insn_info not valid
%rsi
insn_info not valid
Disassembled 1 bytes at 0x400BF5
mov
insn_info not valid
%rsp
insn_info not valid
,
insn_info not valid
%rdx
insn_info not valid
Disassembled 3 bytes at 0x400BF6
and
insn_info not valid
$0xfffffffffffffff0
insn_info not valid
,
insn_info not valid
%rsp
insn_info not valid
Disassembled 4 bytes at 0x400BF9
push
insn_info not valid
%rax
insn_info not valid
Disassembled 1 bytes at 0x400BFD
push
insn_info not valid
%rsp
insn_info not valid
Disassembled 1 bytes at 0x400BFE
mov
insn_info not valid
$0x401450
insn_info not valid
,
insn_info not valid
%r8
insn_info not valid
Disassembled 7 bytes at 0x400BFF
mov
insn_info not valid
$0x4013c0
insn_info not valid
,
insn_info not valid
%rcx
insn_info not valid
Disassembled 7 bytes at 0x400C06
mov
insn_info not valid
$0x4012ce
insn_info not valid
,
insn_info not valid
%rdi
insn_info not valid
Disassembled 7 bytes at 0x400C0D
callq
insn_info not valid
0x0000000000400ad8
insn_info not valid
Disassembled 5 bytes at 0x400C14
hlt
insn_info not valid
Disassembled 1 bytes at 0x400C19
nop
insn_info not valid
Disassembled 1 bytes at 0x400C1A
nop
insn_info not valid
Disassembled 1 bytes at 0x400C1B
sub
insn_info not valid
$0x8
insn_info not valid
,
insn_info not valid
%rsp
insn_info not valid
Disassembled 4 bytes at 0x400C1C
mov
insn_info not valid
0x2013b9(%rip)
insn_info not valid
,
insn_info not valid
%rax
insn_info not valid
#
insn_info not valid
0x0000000000601fe0
insn_info not valid
Disassembled 7 bytes at 0x400C20
test
insn_info not valid
%rax
insn_info not valid
,
insn_info not valid
%rax
insn_info not valid
Disassembled 3 bytes at 0x400C27
je
insn_info not valid
0x0000000000400c2e
insn_info not valid
Disassembled 2 bytes at 0x400C2A
callq
insn_info not valid
*%rax
insn_info not valid
Disassembled 2 bytes at 0x400C2C
add
insn_info not valid
$0x8
insn_info not valid
,
insn_info not valid
%rsp
insn_info not valid
Disassembled 4 bytes at 0x400C2E
retq
Stopped disassembly
insn_info not valid
Disassembled 1 bytes at 0x400C32
What I am expecting is to be able to read instruction information for each instruction through the dinfo->insn_type
, target
, etc. The behaviour is exhibited on both x86-32 and x86-64. If I can at least get confirmation that this is unimplemented on these two architectures then I can go about filling in this information myself.
See Question&Answers more detail:
os