[WIP] Apollo Guidance Computer disassembly (#54)

* Implemented basic structure for AGC plugin.

Implemented integration into build system and layout, as well as some
barebones setup code. Now onto the actual logic :3

* Added basic structure of AGC disassembler

The core disassembly logic has been ported over from
https://github.com/alfonsokim/apollo-guidance-computer/blob/master/yaAGC/agc_disassembler.c .
However, since the disassembler is inherently stateful, we still need
to implement either state or port over the whole lib / rewrite it.
Or I get to find a different solution for this.

* Done some research and gathered some information

It seems clear how to get the current address etc. from the data
passed to the plugin without resorting to internal state. That way
a lot of complexity should be avoidable and implementation gets a
lot easier.

* Cleaned up logic

* Integrated things

* Finished integration of disasm plugin.

* Worked through the AGC instruction set

Gathered info needed for the anal plugin and commented the disassembler
components.

* Fixed compiler error

* Added some constants and fixed errors

* Added help file
This commit is contained in:
ibabushkin authored and radare committed 2016-07-18 10:33:23 +02:00
1 parent 2d7912b5e8
commit 6fa7ca813b
11 files changed
+620

No files matched your search

+82
View File
@@ -0,0 +1,82 @@
VERSION=0.10.4
HAVE_LIB_YARA=0
PREFIX=/usr
DESTDIR?=
TAR=tar -cvf
CZ=xz -f
DIRS=
ifeq ($(HAVE_LIB_YARA),1)
DIRS+=yara/yara2
DIRS+=yara/yara3
DIRS+=libr/asm/p
DIRS+=libr/anal/p
endif
help:
@echo "Usage make [target] .. Targets:"
@echo " all - build everything"
@echo " baleful - build baleful r2 plugin"
@echo " asm - build armthumb, ppc, psosvm, x86bea, m68k plugins"
@echo " anal - build analysis plugins (RAnal)"
@echo " debug - build debugger plugins (RDebug)"
@echo " dlang - support DLang demangler (RBin)"
@echo " yara{2,3} - build yara2 or yara3 r2 plugin"
@echo " (target)-install - install given plugin"
@echo " (target)-clean - clean given plugin"
@echo " install-yara{2,3} - install Yara 2 or 3 from Git"
baleful:
$(MAKE) -C baleful
debug unicorn:
$(MAKE) -C libr/debug/p
anal:
$(MAKE) -C libr/anal/p
asm:
$(MAKE) -C libr/asm/p
asm-install:
$(MAKE) -C libr/asm/p install DESTDIR=$(DESTDIR)
asm-clean:
$(MAKE) -C libr/asm/p clean
dlang:
$(MAKE) -C libr/bin/p
dlang-install:
$(MAKE) -C libr/bin/p install DESTDIR=$(DESTDIR)
yara2 yara3:
cd yara/$@ ; ./configure --prefix=$(PREFIX)
$(MAKE) -C yara/$@
yara2-clean:
$(MAKE) -C yara/yara2 clean
yara3-clean:
$(MAKE) -C yara/yara3 clean
baleful-install:
$(MAKE) -C baleful install DESTDIR=$(DESTDIR)
debug-install unicorn-install:
$(MAKE) -C libr/debug/p install DESTDIR=$(DESTDIR)
debug-uninstall unicorn-uninstall:
$(MAKE) -C libr/debug/p uninstall DESTDIR=$(DESTDIR)
yara2-install:
$(MAKE) -C yara/yara2 install DESTDIR=$(DESTDIR)
yara3-install:
$(MAKE) -C yara/yara3 install DESTDIR=$(DESTDIR)
all:
for a in $(DIRS) ; do \
( cd $$a ; ./configure --prefix=$(PREFIX) ; $(MAKE) ) ; \
done
clean mrproper install symstall uninstall deinstall:
for a in $(DIRS) ; do ( cd $$a ; $(MAKE) $@ ) ; done
dist:
DIR=`basename $$PWD` ; \
FILES=`git ls-files | sed -e s,^,radare2-extras-${VERSION}/,` ; \
cd .. && mv $${DIR} radare2-extras-${VERSION} && \
${TAR} radare2-extras-${VERSION}.tar $${FILES} ; \
${CZ} radare2-extras-${VERSION}.tar ; \
mv radare2-extras-${VERSION} $${DIR}
w32dist:
@echo TODO: w32dist
.PHONY: dist all
+1
View File
@@ -1,5 +1,6 @@
ARCHS=
ARCHS+=agc.mk
ARCHS+=x86_bea.mk
#ARCHS+=x86_im.mk
ARCHS+=x86_simple.mk
+10
View File
@@ -0,0 +1,10 @@
OBJ_AGC=anal_agc.o
STATIC_OBJ+=${OBJ_AGC}
TARGET_AGC=anal_agc.${LIBEXT}
ALL_TARGETS+=${TARGET_AGC}
${TARGET_AGC}: ${OBJ_AGC}
${CC} $(call libname,anal_agc) ${LDFLAGS} \
${CFLAGS} -o anal_agc.${LIBEXT} ${OBJ_AGC}
+36
View File
@@ -0,0 +1,36 @@
#include <r_types.h>
#include <r_lib.h>
#include <r_util.h>
#include <r_anal.h>
static int agc_op(RAnal *anal, RAnalOp *op, ut64 addr, const ut8 *data, int len) {
// TODO: get rid of this, we have that in our disassembler already
static bool extra_opcode_bit = false;
if (!data || len != 2) return -1;
op->size = 2;
op->type = R_ANAL_OP_TYPE_UNK;
if (!extra_opcode_bit) {
} else {
}
return op->size;
}
RAnalPlugin r_anal_plugin_agc = {
.name = "agc",
.desc = "AGC code analysis plugin",
.license = "LGPL3",
.arch = "agc",
.bits = 16,
.op = &agc_op,
};
#ifndef CORELIB
RLibStruct radare_plugin = {
.type = R_LIB_TYPE_ANAL,
.data = &r_anal_plugin_agc,
.version = R2_VERSION
};
#endif
+350
View File
@@ -0,0 +1,350 @@
/*
Copyright 2003-2006 Ronald S. Burkey <[email protected]>,
2008 Onno Hommes
2016 Inokentiy Babushkin
This file is based on the implmentation found in yaAGC, but modified for new
purposes.
This file is part of yaAGC / radare2-extras.
yaAGC 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.
yaAGC 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 yaAGC; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Filename: agc_disassembler.h
Purpose: Source file for AGC Disassembler.
Contact: Onno Hommes
Reference: http://www.ibiblio.org/apollo
Mods: 08/31/08 OH. Began.
07/12/16 IB. Began refactoring to include in radare2-extras.
*/
#include <stdbool.h>
#include <stdio.h>
#include "asm_agc.h"
/*
* We need to find out what the logic behind the modifications done by
* ShowAddressContents() is. Since that function performs some lookup
* on the internal state of the AGC *simulator*, it is very well possible
* we can simplify things a fair bit. This would also mean that we might be
* able to reduce the decoding work to interpreting the EXTEND instruction
* as a prefix, since that's it's only function as far as we know so far.
*
* However, if the assembling/disassembling doesn't yield the desired
* results, we will be in deep trouble already.
*
* So far, the best thing to do is to document the functionality of the
* different static variables employes:
* * sCurrentZ: the current address to be disassembled.
* It appears that they are handled differently depending on their value.
* For instance, the bank gets computed using them (duh).
* * sBank: the memory bank the address maps to.
* Memory bank addressing was used because of storage shortage for
* adresses, which led to this workaround. Banks have different sizes
* (1K or 256 bytes), thus they get used differently in the code.
* * sValue: the memory value at the location denoted by the address.
* * sErasable: whether the data we access is in erasable memory.
* * sFixed: whether we are reading from ROM.
*
* But why are we using two variables to denote mutually exclusive flags?
*
* We can get the current address from the RAsm structure pointer passed
* to disassemble(). Thus, we can compute erasable and fixed.
*
* NOP's seem to be encoded in a position-depend way, but that's all we
* need to handle specially.
*/
#define SWITCH_MASK 070000
#define LOWER_WIDE 07777
#define LOWER 01777
#define HIGHER 06000
void disasm_instruction(unsigned int address, int value, char *buf, int len) {
static bool extra_opcode_bit = false;
// distinguish between RW and RO memory
bool erasable = false;
if (address < 02000)
erasable = true;
// instruction encoding is pretty convoluted, since it doesn't work well with
// byte boundaries. Thus, every decoding operation below is about to get proper
// documentation to allow for better understanding (to write the ESIL and ANAL
// bits still needed).
// also see http://www.ibiblio.org/apollo/assembly_language_manual.html
if (!extra_opcode_bit) {
// simple instructions below - encoded as follows: x iii ooo ooo ooo
// where x is the odd-parity bit, which is used only by the hardware itself
// to determine error conditions. However, yaYUL doesn't emulate it, so things
// are shifted one bit to the left. TODO: maybe we should change this?
// first, special cases that have their own mnemonics
if (value == 040000) // i = 100, o = 0
// complement the A register (address 0)
snprintf(buf, len, "com");
else if (value == 020001) // i = 010, o = 0, 1
// add the A, L register pair to itself (addresses 0 and 1)
snprintf(buf, len, "ddoubl");
else if (value == 060000) // i = 110, o = 0
// add the A register to itself (address 0o)
snprintf(buf, len, "double");
else if (value == 052006)
// change adressing, see manual
snprintf(buf, len, "dtcb");
else if (value == 052005)
// change adressing, see manual
snprintf(buf, len, "dtcf");
else if (value == 6) {
// set extracode flag for next instruction, and the instruction after that,
// if it is an index.
snprintf(buf, len, "extend");
extra_opcode_bit = true;
} else if (value == 4)
// disable interrupts
snprintf(buf, len, "inhint");
else if (!erasable && value == address + 1 && value != 10000)
// NOOP's are special:
// they are encoded as 10000 + current adress in fixed memory...
// (which is a jump to the next instruction)
snprintf(buf, len, "noop");
else if (erasable && value == 030000)
// ...and as a constant value in erasable memory, because the jump method
// isn't applicable. Note that this way of encoding it means loading the
// accumulator (A) with itself.
snprintf(buf, len, "noop");
else if (value == 054000)
// skip the next instruction if A is overflown
snprintf(buf, len, "ovsk");
else if (value == 3)
// enable interrupts after an inhint
snprintf(buf, len, "relint");
else if (value == 050017)
// resume program from an ISR
snprintf(buf, len, "resume");
else if (value == 2)
// return from subroutine, which is to say, move the Q register
// into the Z register.
snprintf(buf, len, "return");
else if (value == 054005) // essentially a `TS Z`
// jump to the address stored in the A register
snprintf(buf, len, "tcaa");
else if (value == 1) // essentially a `TC L`
// convoluted control flow primitive from hell, see manual:
// load an instruction in the `A` register, and a return instruction into
// the `L` register, call `XLQ` and you will execute the contents of `A`
// and return.
snprintf(buf, len, "xlq");
else if (value == 0) // essentially `TC A`
// similar to the above, but with extracode:
// load an `EXTEND` instruction into the `A` register, an extracode
// instruction into the `L` register, a return instruction into the `Q`
// register and call `XXALQ`. This results in the execution of the given
// extracode instruction.
snprintf(buf, len, "xxalq");
else if (value == 022007) // essentially `LXCH 7`
// zero the `L` register, by exchanging it's content with the 7th register,
// which is hardwired to zero.
snprintf(buf, len, "zl");
else switch (value & SWITCH_MASK) {
// from now on, we have instructions with operands, so switch based on
// the first three bits.
case 000000: // i = 000, o = address of subroutine
// call a subroutine (this can't be nested, because it stores the
// current contents of the `Z` register in the `Q` register).
snprintf(buf, len, "tc\t%04o", value & LOWER_WIDE);
break;
case 010000: // i = 001
if (!(value & 06000)) // one of the many space-saving tricks employed
// weird compare and jump (10 bit address in erasable memory)
snprintf(buf, len, "ccs\t%04o",value & LOWER);
else
// jump to a memory location (12 bit address in fixed memory)
snprintf(buf, len, "tcf\t%04o", value & LOWER_WIDE);
break;
case 020000: // i = 010
switch (value & 06000) { // lower bits...
case 00000:
// add the `A`, `L` register pair to a memory location
snprintf(buf, len, "das\t%04o", (value - 1) & LOWER);
break;
case 02000:
// exchange the L register and a memory location
snprintf(buf, len, "lxch\t%04o", value & LOWER);
break;
case 04000:
// increment a memory location
snprintf(buf, len, "incr\t%04o", value & LOWER);
break;
case 06000:
// add the `A` register to a memory location
snprintf(buf, len, "ads\t%04o", value & LOWER);
break;
}
break;
case 030000: // i = 011
// move a memory location's contents into the `A` register
snprintf(buf, len, "ca\t%04o", value & LOWER_WIDE);
break;
case 040000: // i = 100
// move a memory location's complement into the `A` register
snprintf(buf, len, "cs\t%04o", value & LOWER_WIDE);
break;
case 050000: // i = 101
switch (value & HIGHER) { // lower bits again...
case 00000:
// adds the contents of a memory location to the next
// instruction before it is executed, without altering memory
snprintf(buf, len, "index\t%04o", value & LOWER);
break;
case 02000:
// exchange double precision int with `A`, `L` register pair
snprintf(buf, len, "dxch\t%04o", (value - 1) & LOWER);
break;
case 04000:
// copy the contents of the `A` register to memory, with extra
// magic when overflow occured.
snprintf(buf, len, "ts\t%04o", value & LOWER);
break;
case 06000:
// exchange an erasable memory location with the `A` register
snprintf(buf, len, "xch\t%04o", value & LOWER);
break;
}
break;
case 060000: // i = 110
// add a memory location to the `A` register
snprintf(buf, len, "ad\t%04o", value & LOWER_WIDE);
break;
case 070000: // i = 111
// logical and between the `A` register and a memory location
snprintf(buf, len, "mask\t%04o", value & LOWER_WIDE);
break;
}
} else {
// the previous instruction was an `EXTEND`. This means we will interpret
// not three, but four leading bits of our intruction as an opcode. This
// was added to the second-generation AGC, after it became clear that three
// bits for an opcode are not enough.
extra_opcode_bit = false;
if (value == 040001) // basically a `DCS A`
// bitwise complement of the register pair `A`, `L`
snprintf(buf, len, "dcom");
else if (value == 050017) //
// resume program from an ISR
snprintf(buf, len, "resume"); // FIXME: wtf is this here? see manual...
else if (value == 070000) // essentially `MP A`
// square the contents of register `A`
snprintf(buf, len, "square");
else if (value == 022007) // essentially `QXCH 7`
// zero the `Q` register, by exchanging it's content with the 7th register,
// which is hardwired to zero.
snprintf(buf, len, "zq");
else switch (value & SWITCH_MASK) {
case 000000: // i = 000
switch (value & 07000) { // lower bits
case 00000:
// read a 9 bit IO channel into register `A`
snprintf(buf, len, "read\t%03o", value & 0777);
break;
case 01000:
// write register `A` into a 9 bit IO channel
snprintf(buf, len, "write\t%03o", value & 0777);
break;
case 02000:
// read and bitwise and
snprintf(buf, len, "rand\t%03o", value & 0777);
break;
case 03000:
// read and bitwise and with writeback
snprintf(buf, len, "wand\t%03o", value & 0777);
break;
case 04000:
// read and bitwise or
snprintf(buf, len, "ror\t%03o", value & 0777);
break;
case 05000:
// read and bitwise or with writeback
snprintf(buf, len, "wor\t%03o", value & 0777);
break;
case 06000:
// read and xor
snprintf(buf, len, "rxor\t%03o", value & 0777);
break;
case 07000:
// noone knows o__O
snprintf(buf, len, "edrupt\t%03o", value & 0777);
break;
}
break;
case 010000: // i = 001
if (!(value & HIGHER))
// divide register pair `A`, `L` by a memory location,
// putting the quotient/remainder in `A`, `L`, respectively
snprintf(buf, len, "dv\t%04o", value & LOWER);
else
// branch to a fixed memory location if `A` equals 0
snprintf(buf, len, "bzf\t%04o", value & LOWER_WIDE);
break;
case 020000: // i = 010
switch (value & HIGHER) {
case 00000:
// 1's complement difference between two 2's complement values
// of which the first is the register `A`
snprintf(buf, len, "msu\t%04o", value & LOWER);
break;
case 02000:
// exchange `Q` register with memory location
snprintf(buf, len, "qxch\t%04o", value & LOWER);
break;
case 04000:
// increment positive and decrement negative values at a
// memory location, using 1's complement
snprintf(buf, len, "aug\t%04o", value & LOWER);
break;
case 06000:
// other way 'round
snprintf(buf, len, "dim\t%04o", value & LOWER);
break;
}
break;
case 030000: // i = 011
// move a pair of registers into `A` and `L`
snprintf(buf, len, "dca\t%04o", (value - 1) & LOWER_WIDE);
break;
case 040000: // i = 100
// move the complement of a pair of registers into `A` and `L`
snprintf(buf, len, "dcs\t%04o", (value - 1) & LOWER_WIDE);
break;
case 050000: // i = 101
// see above
snprintf(buf, len, "index\t%04o", value & LOWER_WIDE);
// reset bit
extra_op_code_bit = true;
break;
case 060000: // i = 110
if (!(value & HIGHER))
// subtract a memory location from the `A` register
snprintf(buf, len, "su\t%04o", value & LOWER);
else
// branch to a fixed memory location if `A` is equal or less than 0
snprintf(buf, len, "bzmf\t%04o", value & LOWER_WIDE);
break;
case 070000: // i = 111
// multiply two single precision values to form a double precision
// value, of which one is the accumulator.
snprintf(buf, len, "mp\t%04o", value & LOWER_WIDE);
break;
}
}
}
+6
View File
@@ -0,0 +1,6 @@
#ifndef INCLUDED_AGC_H
#define INCLUDED_AGC_H
void disasm_instruction(unsigned int address, int value, char *buf, int len);
#endif
+54
View File
@@ -0,0 +1,54 @@
com=complement the `A` register (address 0)
ddoubl=add the A, `L` register pair to itself (addresses 0 and 1)
double=add the `A` register to itself (address 0o)
dtcb=change adressing, see manual
dtcf=change adressing, see manual
extend=set extracode flag for next instruction, and the instruction after that, if it is an index.
inhint=disable interrupts
noop=no operation, but note that this is encoded in a context-sensitive fashion
ovsk=skip the next instruction if `A` is overflown
relint=enable interrupts after an inhint
resume=resume program from an ISR
return=return from subroutine, which is to say, move the `Q` register into the `Z` register
tcaa=jump to the address stored in the `A` register
xlq=execute the content of the `A` register, assuming there is a `return` instruction in `L`
xxalq=execute the content of the `L` register as an extracode instruction, assuming there is an `extend` instruction in the `A` register and a `return` instruction in `Q`
zl=zero the `L` register
tc=call a subroutine, can't be nested
ccs=weird compare and jump (10 bit address in erasable memory)
tcf=jump to a memory location (12 bit address in fixed memory)
das=add the `A`, `L` register pair to a memory location
lxch=exchange the `L` register and a memory location
incr=increment a memory location
ads=add the `A` register to a memory location
ca=move a memory location's contents into the `A` register
cs=move a memory location's complement into the `A` register
index=add a memory location to the next instruction before it is executed, without altering memory
dxch=exchange double precision int with `A`, `L` register pair
ts=copy the contents of the `A` register to memory, with extra magic when overflow occured.
xch=exchange an erasable memory location with the `A` register
ad=add a memory location to the `A` register
mask=logical and between the `A` register and a memory location
dcom=bitwise complement of the register pair `A`, `L`
resume=resume program from an ISR
square=square the contents of register `A`
zq=zero the `Q` register
read=read a 9 bit IO channel into register `A`
write=write register `A` into a 9 bit IO channel
rand=read and bitwise and
wand=read and bitwise and with writeback
ror=read and bitwise or
wor=read and bitwise or with writeback
rxor=read and xor
edrupt=noone knows o__O
dv=divide register pair `A`, `L` by a memory location (double precision), putting the quotient/remainder in `A`, `L`, respectively
bzf=branch to a fixed memory location if `A` equals 0
msu=1's complement difference between two 2's complement values, of which the first is the register `A`
qxch=exchange `Q` register with memory location
aug=increment positive and decrement negative values at a memory location, using 1's complement
dim=other way 'round compared to `aug`
dca=move a pair of registers into `A` and `L`
dcs=move the complement of a pair of registers into `A` and `L`
su=subtract a memory location from the `A` register
bzmf=branch to a fixed memory location if `A` is equal or less than 0
mp=multiply two single precision values to form a double precision value, of which one is the accumulator.
+1
View File
@@ -1,5 +1,6 @@
ARCHS=
ARCHS+=agc.mk
ARCHS+=armthumb.mk
ARCHS+=ppc_disasm.mk
ARCHS+=psosvm.mk
+11
View File
@@ -0,0 +1,11 @@
OBJ_AGC=asm_agc.o
OBJ_AGC+=../arch/agc/asm_agc.o
STATIC_OBJ+=${OBJ_AGC}
TARGET_AGC=asm_agc.${LIBEXT}
ALL_TARGETS+=${TARGET_AGC}
${TARGET_AGC}: ${OBJ_AGC}
${CC} $(call libname,asm_armthumb) ${LDFLAGS} \
${CFLAGS} -o ${TARGET_AGC} ${OBJ_AGC}
+36
View File
@@ -0,0 +1,36 @@
#include <r_lib.h>
#include <r_asm.h>
#include "../arch/agc/asm_agc.h"
static int disassemble(RAsm *a, RAsmOp *op, const ut8 *buf, int len) {
op->buf_asm[0] = 0;
disasm_instruction(a->pc,
((const ut16 *)buf)[0], op->buf_asm, R_ASM_BUFSIZE);
op->size = 2;
return op->size;
}
static int assemble(RAsm *a, RAsmOp *op, const char *buf) {
// TODO
return 0;
}
RAsmPlugin r_asm_plugin_agc = {
.name = "agc",
.arch = "agc",
.bits = 16,
.license = "GPL3",
.desc = "AGC (Apollo Guidance Computer) disassembly plugin",
.init = NULL,
.fini = NULL,
.disassemble = &disassemble,
.assemble = &assemble
};
#ifndef CORELIB
struct r_lib_struct_t radare_plugin = {
.type = R_LIB_TYPE_ASM,
.data = &r_asm_plugin_agc,
.version = R2_VERSION
};
#endif
+33
View File
@@ -0,0 +1,33 @@
# TEST
CC=gcc
PREFIX=/usr
HOST_OS=linux
VERSION=0.10.4
LIBDIR=/usr/lib
INSTALL_LIB=/usr/bin/install -m 755 -c
USEROSTYPE=auto
HAVE_PKGCFG_UNICORN=0
UC_CFLAGS=
UC_LDFLAGS=
R2_CFLAGS=-I/usr/include/libr
R2_LDFLAGS=-lr_core -lr_config -lr_cons -lr_io -lr_util -lr_flags -lr_asm -lr_debug -lr_hash -lr_bin -lr_lang -lr_io -lr_anal -lr_parse -lr_bp -lr_egg -lr_reg -lr_search -lr_syscall -lr_socket -lr_fs -lr_magic
HAVE_LIB_EWF=0
EWF_CFLAGS=
EWF_LDFLAGS=
#CFLAGS+=$(R2_CFLAGS)
#LDFLAGS+=$(R2_LDFLAGS)
ifeq ($(HOST_OS),darwin)
LIBEXT?=dylib
else
ifeq ($(HOST_OS),windows)
LIBEXT?=dll
else
LIBEXT?=so
endif
endif