readelf: rewrite in Erlang

This commit is contained in:
Mikael Pettersson 2019-09-01 14:33:30 +02:00
parent a255160ff7
commit e410aa86da
4 changed files with 630 additions and 7 deletions

View File

@ -21,7 +21,7 @@ REBAR3=$(shell type -p rebar3 || echo ./rebar3)
REBAR3_GIT=https://github.com/erlang/rebar3.git
REBAR3_VSN=3.7.5
PROGRAMS=8to9 ar as nm od
PROGRAMS=8to9 ar as nm od readelf
default: compile link

View File

@ -0,0 +1,25 @@
%%% Copyright (C) 2019 Mikael Pettersson
%%%
%%% This file is part of pdp10-tools.
%%%
%%% pdp10-tools 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 3 of the License, or
%%% (at your option) any later version.
%%%
%%% pdp10-tools 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 pdp10-tools. If not, see <http://www.gnu.org/licenses/>.
{application, readelf,
[{description, "'readelf' clone for pdp10-elf"},
{vsn, "0.1.0"},
{registered, []},
{applications, [kernel, stdlib, lib]},
{env, []},
{modules, []}
]}.

View File

@ -0,0 +1,598 @@
%%% -*- erlang-indent-level: 2 -*-
%%%
%%% 'readelf' clone for pdp10-elf
%%% Copyright (C) 2013-2019 Mikael Pettersson
%%%
%%% This file is part of pdp10-tools.
%%%
%%% pdp10-tools 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 3 of the License, or
%%% (at your option) any later version.
%%%
%%% pdp10-tools 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 pdp10-tools. If not, see <http://www.gnu.org/licenses/>.
-module(readelf).
-export([main/1]).
-include_lib("lib/include/pdp10_elf36.hrl").
-include_lib("lib/include/pdp10_opcodes.hrl").
-record(options,
{ file_header = false
, segments = false
, sections = false
, section_groups = false
, section_details = false
, symbols = false
, dyn_syms = false
, notes = false
, relocs = false
, unwind = false
, dynamic = false
, version_info = false
, arch_specific = false
, use_dynamic = false
, archive_index = false
, histogram = false
, disassemble = false % extension
}).
%% Command-line interface ======================================================
main(Argv) ->
escript_runtime:start(fun main_/1, Argv).
main_(Argv) ->
case getopt:parse(Argv, "ahlSgtesnrudVADcIvW",
[
%% long-only options
{ "dyn-syms", no, dyn_syms }
, { "disassemble", no, disassemble } % extension
%% long aliases for short options
, { "all", no, $a }
, { "file-header", no, $h }
, { "program-headers", no, $l }
, { "segments", no, $l }
, { "section-groups", no, $g }
, { "section-details", no, $t }
, { "headers", no, $e }
, { "symbols", no, $s }
, { "syms", no, $s }
, { "notes", no, $n }
, { "relocs", no, $r }
, { "unwind", no, $u }
, { "version-info", no, $V }
, { "arch-specific", no, $A }
, { "use-dynamic", no, $D }
, { "archive-index", no, $c }
, { "histogram", no, $I }
, { "version", no, $v }
, { "wide", no, $W }
%% --{hex,string,relocated}-dump: NYI
%% --debug-dump: NYI
%% --dwarf-{depth,start}: NYI
%% --decompress: NYI
%% --help: NYI
%% @file: NYI
]) of
{ok, {Options, Files}} ->
Opts = scan_options(Options),
case Files of
[] -> usage();
_ ->
case readelf(Opts, Files) of
ok -> halt(0);
{error, Reason} ->
escript_runtime:fatal("~s\n", [error:format(Reason)])
end
end;
{error, Reason} ->
escript_runtime:errmsg("~s\n", [error:format(Reason)]),
usage()
end.
usage() ->
escript_runtime:fmterr(
"Usage: ~s -[ahlSgtesnrudVADcIvW] elffile..\n",
[escript_runtime:progname()]),
halt(1).
scan_options(Options) ->
lists:foldl(fun scan_option/2, #options{}, Options).
scan_option(Option, Opts) ->
case Option of
$a ->
Opts#options{ file_header = true
, segments = true
, sections = true
, symbols = true
, relocs = true
, dynamic = true
, notes = true
, version_info = true
, arch_specific = true
, unwind = true
, section_groups = true
, histogram = true };
$h ->
Opts#options{ file_header = true };
$l ->
Opts#options{ segments = true };
$S ->
Opts#options{ sections = true };
$g ->
Opts#options{ section_groups = true };
$t ->
Opts#options{ section_details = true
, sections = true };
$s ->
Opts#options{ symbols = true };
dyn_syms ->
Opts#options{ dyn_syms = true };
$e ->
Opts#options{ file_header = true
, segments = true
, sections = true };
$n ->
Opts#options{ notes = true };
$r ->
Opts#options{ relocs = true };
$u ->
Opts#options{ unwind = true };
$d ->
Opts#options{ dynamic = true };
$V ->
Opts#options{ version_info = true };
$A ->
Opts#options{ arch_specific = true };
$D ->
Opts#options{ use_dynamic = true };
$c ->
Opts#options{ archive_index = true };
$I ->
Opts#options{ histogram = true };
$v ->
version();
$W ->
Opts; % --wide is default in this readelf
disassemble -> % extension
Opts#options{ disassemble = true }
end.
version() ->
io:format(standard_io, "pdp10-tools readelf version 0.1\n", []),
halt(0).
%% readelf =====================================================================
readelf(_Opts, []) -> ok;
readelf(Opts, [File | Files]) ->
case readelf_file(Opts, File) of
ok -> readelf(Opts, Files);
{error, _Reason} = Error -> Error
end.
readelf_file(Opts, File) ->
case pdp10_stdio:fopen(File, [raw, read]) of
{ok, FP} ->
try readelf_ehdr(Opts, FP)
after pdp10_stdio:fclose(FP) end;
{error, _Reason} = Error -> Error
end.
readelf_ehdr(Opts, FP) ->
case pdp10_elf36:read_Ehdr(FP) of
{ok, Ehdr} ->
case print_ehdr(Opts, Ehdr) of
ok -> readelf_shtab(Opts, FP, Ehdr);
{error, _Reason} = Error -> Error
end;
{error, _Reason} = Error -> Error
end.
readelf_shtab(Opts, FP, Ehdr) ->
case pdp10_elf36:read_ShTab(FP, Ehdr) of
{ok, ShTab} ->
case print_shtab(Opts, ShTab) of
ok -> readelf_symtab(Opts, FP, ShTab);
{error, _Reason} = Error -> Error
end;
{error, _Reason} = Error -> Error
end.
readelf_symtab(Opts, FP, ShTab) ->
case pdp10_elf36:read_SymTab(FP, ShTab) of
{ok, {SymTab, ShNdx}} ->
case print_symtab(Opts, SymTab, ShNdx, ShTab) of
ok -> disassemble(Opts, FP, ShTab, SymTab);
{error, _Reason} = Error -> Error
end;
{error, _Reason} = Error -> Error
end.
%% print_ehdr ==================================================================
print_ehdr(#options{file_header = false}, _Ehdr) -> ok;
print_ehdr(_Opts, Ehdr = #elf36_Ehdr{}) ->
EIdent = Ehdr#elf36_Ehdr.e_ident,
io:format("ELF Header:\n"),
print_e_ident(EIdent),
print_ei_class(lists:nth(1 + ?EI_CLASS, EIdent)),
print_ei_data(lists:nth(1 + ?EI_DATA, EIdent)),
print_ei_version(lists:nth(1 + ?EI_VERSION, EIdent)),
print_ei_osabi(lists:nth(1 + ?EI_OSABI, EIdent)),
print_ei_abiversion(lists:nth(1 + ?EI_ABIVERSION, EIdent)),
print_e_type(Ehdr#elf36_Ehdr.e_type),
print_e_machine(Ehdr#elf36_Ehdr.e_machine),
print_e_version(Ehdr#elf36_Ehdr.e_version),
print_e_entry(Ehdr#elf36_Ehdr.e_entry),
print_e_phoff(Ehdr#elf36_Ehdr.e_phoff),
print_e_shoff(Ehdr#elf36_Ehdr.e_shoff),
print_e_flags(Ehdr#elf36_Ehdr.e_flags),
print_e_ehsize(Ehdr#elf36_Ehdr.e_ehsize),
print_e_phentsize(Ehdr#elf36_Ehdr.e_phentsize),
print_e_phnum(Ehdr#elf36_Ehdr.e_phnum),
print_e_shentsize(Ehdr#elf36_Ehdr.e_shentsize),
print_e_shnum(Ehdr#elf36_Ehdr.e_shnum),
print_e_shstrndx(Ehdr#elf36_Ehdr.e_shstrndx),
io:format("\n").
print_e_ident(EIdent) ->
io:format(" Magic:\t\t\t\t"),
print_e_ident("", EIdent),
io:format("\n").
print_e_ident(_Pfx, []) -> ok;
print_e_ident(Pfx, [Byte | Bytes]) ->
io:format("~s", [Pfx]),
%% bytes in [256,512[ are unlikely, but handle them correctly anyway
if Byte > 255 -> io:format("~3.16.0b", [Byte]);
true -> io:format("~2.16.0b", [Byte])
end,
print_e_ident(" ", Bytes).
print_ei_class(EIClass) ->
io:format(" Class:\t\t\t\t~.10b (~s)\n",
[EIClass,
case EIClass of
?ELFCLASS32 -> "ELF32";
?ELFCLASS64 -> "ELF64";
?ELFCLASS36 -> "ELF36";
_ -> "?"
end]).
print_ei_data(EIData) ->
io:format(" Data:\t\t\t\t\t~.10b (~s)\n",
[EIData,
case EIData of
?ELFDATA2LSB -> "2's complement, little endian";
?ELFDATA2MSB -> "2's complement, big endian";
_ -> "?"
end]).
print_ei_version(EIVersion) ->
io:format(" Version:\t\t\t\t~.10b (~s)\n",
[EIVersion, version_string(EIVersion)]).
print_ei_osabi(EIOSABI) ->
io:format(" OS/ABI:\t\t\t\t~.10b (~s)\n",
[EIOSABI,
case EIOSABI of
?ELFOSABI_NONE -> "Generic";
?ELFOSABI_LINUX -> "Linux";
_ -> "?"
end]).
print_ei_abiversion(EIABIVersion) ->
io:format(" ABI Version:\t\t\t\t~.10b\n", [EIABIVersion]).
print_e_type(EType) ->
io:format(" Type:\t\t\t\t\t~.10b (~s)\n",
[EType,
case EType of
?ET_REL -> "Relocatable file";
?ET_EXEC -> "Executable file";
?ET_DYN -> "Shared object file";
?ET_CORE -> "Core file";
_ -> "?"
end]).
print_e_machine(EMachine) ->
io:format(" Machine:\t\t\t\t~.10b (~s)\n",
[EMachine,
case EMachine of
?EM_PDP10 -> "Digital Equipment Corp. PDP-10";
_ -> "?"
end]).
print_e_version(EVersion) ->
io:format(" Version:\t\t\t\t~.10b (~s)\n",
[EVersion, version_string(EVersion)]).
version_string(?EV_CURRENT) -> "current";
version_string(_) -> "?".
print_e_entry(EEntry) ->
io:format(" Entry point address:\t\t\t0x~.16b\n", [EEntry]).
print_e_phoff(EPhOff) ->
io:format(" Start of program headers:\t\t~.10b\n", [EPhOff]).
print_e_shoff(EShOff) ->
io:format(" Start of section headers:\t\t~.10b\n", [EShOff]).
print_e_flags(EFLags) ->
io:format(" Flags:\t\t\t\t0x~.16b\n", [EFLags]).
print_e_ehsize(EEhSize) ->
io:format(" Size of this header:\t\t\t~.10b\n", [EEhSize]).
print_e_phentsize(EPhEntSize) ->
io:format(" Size of program headers:\t\t~.10b\n", [EPhEntSize]).
print_e_phnum(EPhNum) ->
io:format(" Number of program headers:\t\t~.10b\n", [EPhNum]).
print_e_shentsize(EShEntSize) ->
io:format(" Size of section headers:\t\t~.10b\n", [EShEntSize]).
print_e_shnum(EShNum) ->
io:format(" Number of section headers:\t\t~.10b\n", [EShNum]).
print_e_shstrndx(EShStrNdx) ->
io:format(" Section header string table index:\t~.10b\n", [EShStrNdx]).
%% print_shtab =================================================================
print_shtab(#options{sections = false}, _ShTab) -> ok;
print_shtab(_Opts, ShTab) ->
io:format("Section Headers:\n"),
io:format(" [Nr] Name Type Addr Off Size ES Flg Lk Inf Al\n"),
print_shdrs(ShTab, 0).
print_shdrs([Shdr | Shdrs], I) ->
io:format(" [~.10b] ~s ~s 0x~.16b ~.10b ~.10b ~.10b ~s ~.10b ~.10b ~.10b\n",
[ I
, sh_name(Shdr)
, sh_type(Shdr)
, Shdr#elf36_Shdr.sh_addr
, Shdr#elf36_Shdr.sh_offset
, Shdr#elf36_Shdr.sh_size
, Shdr#elf36_Shdr.sh_entsize
, sh_flags(Shdr)
, Shdr#elf36_Shdr.sh_link
, Shdr#elf36_Shdr.sh_info
, Shdr#elf36_Shdr.sh_addralign
]),
print_shdrs(Shdrs, I + 1);
print_shdrs([], _I) ->
io:format("Key to Flags:\n"),
io:format(" W (write), A (alloc), X (execute), M (merge), S (strings), Z (compressed)\n"),
io:format(" I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)\n"),
io:format(" O (extra OS processing required), o (OS specific), p (processor specific)\n"),
io:format("\n").
sh_name(#elf36_Shdr{sh_name = ShName}) ->
case ShName of
"" -> "(empty)";
_ -> ShName
end.
sh_type(#elf36_Shdr{sh_type = ShType}) ->
case ShType of
?SHT_NULL -> "NULL";
?SHT_PROGBITS -> "PROGBITS";
?SHT_SYMTAB -> "SYMTAB";
?SHT_STRTAB -> "STRTAB";
?SHT_RELA -> "RELA";
?SHT_HASH -> "HASH";
?SHT_DYNAMIC -> "DYNAMIC";
?SHT_NOTE -> "NOTE";
?SHT_NOBITS -> "NOBITS";
?SHT_REL -> "REL";
?SHT_SHLIB -> "SHLIB";
?SHT_DYNSYM -> "DYNSYM";
?SHT_INIT_ARRAY -> "INIT_ARRAY";
?SHT_FINI_ARRAY -> "FINI_ARRAY";
?SHT_PREINIT_ARRAY -> "PREINIT_ARRAY";
?SHT_GROUP -> "GROUP";
?SHT_SYMTAB_SHNDX -> "SYMTAB_SHNDX";
?SHT_GNU_INCREMENTAL_INPUTS -> "GNU_INCREMENTAL_INPUTS";
?SHT_GNU_ATTRIBUTES -> "GNU_ATTRIBUTES";
?SHT_GNU_HASH -> "GNU_HASH";
?SHT_GNU_LIBLIST -> "GNU_LIBLIST";
?SHT_GNU_verdef -> "GNU_verdef";
?SHT_GNU_verneed -> "GNU_verneed";
?SHT_GNU_versym -> "GNU_versym";
_ -> io_lib:format("~.10b", [ShType])
end.
sh_flags(#elf36_Shdr{sh_flags = ShFlags}) ->
sh_flags("WAXxMSILOGTZ", 0, ShFlags, 0, []).
sh_flags([C | Flags], I, ShFlags, Mask, Acc) ->
{NewMask, NewAcc} =
case I =/= 3 andalso (ShFlags band (1 bsl I)) =/= 0 of
true -> {Mask bor (1 bsl I), [C | Acc]};
false -> {Mask, Acc}
end,
sh_flags(Flags, I + 1, ShFlags, NewMask, NewAcc);
sh_flags([], _I, ShFlags, Mask, Acc) ->
{NewMask, NewAcc} =
case ShFlags band ?SHF_EXCLUDE =/= 0 of
true -> {Mask bor ?SHF_EXCLUDE, [$E | Acc]};
false -> {Mask, Acc}
end,
case ShFlags of
0 -> "-";
NewMask -> lists:reverse(NewAcc);
_ -> io_lib:format("0x~.16b", [ShFlags])
end.
%% print_symtab ================================================================
print_symtab(#options{symbols = false}, _SymTab, _ShNdx, _ShTab) -> ok;
print_symtab(_Opts, SymTab, ShNdx, ShTab) ->
Shdr = lists:nth(1 + ShNdx, ShTab),
io:format("Symbol table '~s' in section ~.10b contains ~.10b entries:\n",
[Shdr#elf36_Shdr.sh_name, ShNdx, length(SymTab)]),
io:format(" Num Value Size Type Bind Vis Ndx Name\n"),
print_syms(SymTab, 0).
print_syms([Sym | Syms], I) ->
io:format(" ~.10b 0x~.16b ~.10b ~s ~s ~s ~.10b ~s\n",
[ I
, Sym#elf36_Sym.st_value
, Sym#elf36_Sym.st_size
, st_type(Sym)
, st_bind(Sym)
, st_vis(Sym)
, Sym#elf36_Sym.st_shndx
, st_name(Sym)
]),
print_syms(Syms, I + 1);
print_syms([], _I) ->
io:format("\n").
st_type(#elf36_Sym{st_info = StInfo}) ->
case ?ELF36_ST_TYPE(StInfo) of
?STT_NOTYPE -> "NOTYPE";
?STT_OBJECT -> "OBJECT";
?STT_FUNC -> "FUNC";
?STT_SECTION -> "SECTION";
?STT_FILE -> "FILE";
?STT_COMMON -> "COMMON";
?STT_TLS -> "TLS";
?STT_RELC -> "RELC";
?STT_SRELC -> "SRELC";
?STT_GNU_IFUNC -> "GNU_IFUNC";
StType -> io_lib:format("~.10b", [StType])
end.
st_bind(#elf36_Sym{st_info = StInfo}) ->
case ?ELF36_ST_BIND(StInfo) of
?STB_LOCAL -> "LOCAL";
?STB_GLOBAL -> "GLOBAL";
?STB_WEAK -> "WEAK";
?STB_GNU_UNIQUE -> "GNU_UNIQUE";
StBind -> io_lib:format("~.10b", [StBind])
end.
st_vis(#elf36_Sym{st_other = StOther}) ->
case ?ELF36_ST_VISIBILITY(StOther) of
?STV_DEFAULT -> "DEFAULT";
?STV_INTERNAL -> "INTERNAL";
?STV_HIDDEN -> "HIDDEN";
?STV_PROTECTED -> "PROTECTED";
StVis -> io_lib:format("~.10b", [StVis])
end.
st_name(#elf36_Sym{st_name = StName}) ->
case StName of
"" -> "(empty)";
_ -> StName
end.
%% disassemble =================================================================
disassemble(#options{disassemble = false}, _FP, _ShTab, _SymTab) -> ok;
disassemble(_Opts, FP, ShTab, SymTab) ->
disassemble_sections(ShTab, 0, FP, SymTab).
disassemble_sections([], _ShNdx, _FP, _SymTab) -> ok;
disassemble_sections([Shdr | ShTab], ShNdx, FP, SymTab) ->
case disassemble_section(Shdr, ShNdx, FP, SymTab) of
ok -> disassemble_sections(ShTab, ShNdx + 1, FP, SymTab);
{error, _Reason} = Error -> Error
end.
disassemble_section(Shdr, ShNdx, FP, SymTab) ->
case Shdr of
#elf36_Shdr{ sh_type = ?SHT_PROGBITS
, sh_flags = ?SHF_ALLOC bor ?SHF_EXECINSTR
, sh_offset = ShOffset
, sh_size = ShSize
} ->
io:format("Disassembly of section nr ~.10b ~s:\n\n",
[ShNdx, sh_name(Shdr)]),
case pdp10_stdio:fseek(FP, {bof, ShOffset}) of
ok -> disassemble_insns(0, ShSize, FP, labels(SymTab, ShNdx));
{error, _Reason} = Error -> Error
end;
#elf36_Shdr{} ->
ok
end.
disassemble_insns(Offset, Size, FP, Labels) when Offset < Size ->
case pdp10_elf36:read_uint36(FP) of
{ok, InsnWord} ->
RestLabels = print_labels(Labels, Offset),
io:format(" 0x~.16b:\t0~12.8.0b\t", [Offset, InsnWord]),
disassemble_insn(InsnWord),
disassemble_insns(Offset + 4, Size, FP, RestLabels);
{error, _Reason} = Error -> Error
end;
disassemble_insns(_ShOffset, _ShSize, _FP, _Labels) -> ok.
disassemble_insn(InsnWord) ->
Models = ?PDP10_KL10_271up,
High13 = InsnWord bsr (36 - 13),
Extended = false,
Section0 = false,
case pdp10_opcodes:insn_from_high13(Models, High13, Extended, Section0) of
#pdp10_insn_desc{name = Name, format = Format} ->
io:format("~s ", [Name]),
case Format of
?PDP10_INSN_A_OPCODE -> ok;
_ -> io:format("~.10b,", [(InsnWord bsr 23) band 16#F])
end,
case InsnWord band (1 bsl 22) of
0 -> ok;
_ -> io:format("@")
end,
io:format("~.10b", [InsnWord band ((1 bsl 18) - 1)]),
case (InsnWord bsr 18) band 16#F of
0 -> ok;
IxReg -> io:format("(~.10b)", [IxReg])
end,
io:format("\n");
false ->
io:format("(bad)\n")
end.
print_labels([], _Offset) -> [];
print_labels(Labels = [Label | RestLabels], Offset) ->
#elf36_Sym{st_value = StValue} = Label,
if StValue < Offset -> print_labels(RestLabels, Offset);
StValue =:= Offset ->
io:format("0x~9.16.0b <~s>:\n", [Offset, st_name(Label)]),
print_labels(RestLabels, Offset);
true -> Labels
end.
labels(SymTab, TextShNdx) ->
lists:sort(fun sym_cmp_by_value/2,
lists:filter(fun(Sym) -> sym_is_label(Sym, TextShNdx) end,
SymTab)).
sym_is_label(Sym, TextShNdx) ->
#elf36_Sym{st_shndx = StShNdx, st_info = StInfo} = Sym,
StShNdx =:= TextShNdx andalso
?ELF36_ST_TYPE(StInfo) =:= ?STT_FUNC andalso % TODO: type of a label?
(case ?ELF36_ST_BIND(StInfo) of
?STB_GLOBAL -> true;
?STB_LOCAL -> true;
?STB_WEAK -> true;
_ -> false
end).
sym_cmp_by_value(Sym1, Sym2) ->
Sym1#elf36_Sym.st_value =< Sym2#elf36_Sym.st_value.

View File

@ -32,10 +32,10 @@
{escript_emu_args, "%%! +sbtu +A1 +Bd -noshell -smp auto\n"}.
{profiles,
[
{'8to9', [{escript_main_app, '8to9'}]}
, {ar, [{escript_main_app, ar}]}
, {as, [{escript_main_app, as}]}
, {nm, [{escript_main_app, nm}]}
, {od, [{escript_main_app, od}]}
[ {'8to9', [{escript_main_app, '8to9'}]}
, {ar, [{escript_main_app, ar}]}
, {as, [{escript_main_app, as}]}
, {nm, [{escript_main_app, nm}]}
, {od, [{escript_main_app, od}]}
, {readelf, [{escript_main_app, readelf}]}
]}.