Files
mikpe.pdp10-tools/erlang/apps/as/src/output.erl
2020-03-03 23:16:59 +01:00

679 lines
22 KiB
Erlang

%%% -*- erlang-indent-level: 2 -*-
%%%
%%% ELF output for pdp10-elf as
%%% Copyright (C) 2013-2020 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/>.
%%%
%%%-----------------------------------------------------------------------------
%%%
%%% Output file layout:
%%%
%%% ELF header
%%% <sections directly defined by the input>
%%% <.strtab, if any symbols>
%%% <.symtab, if any symbols>
%%% <.shstrtab, if any sections>
%%% <section header table, if any sections>
%%%
%%% Processing steps:
%%%
%%% initialize context
%%% for each section:
%%% - add name to .shstrtab, assign sh_name
%%% - assign sh_offset and shndx
%%% - if section contains relocations, ensure all their symbols are in symtab
%%% - update context
%%% for each symbol:
%%% - add name to .strtab, assign st_name
%%% - assign st_shndx
%%% order the symbols and record their symtab indices
%%% append .strtab to list of sections
%%% append .symtab to list of sections
%%% append .shstrtab to list of sections
-module(output).
-export([ tunit/2
, format_error/1
]).
-include("tunit.hrl").
-include_lib("lib/include/pdp10_elf36.hrl").
-record(strtab,
{ map :: #{string() => non_neg_integer()}
, dot :: pos_integer()
}).
-record(context,
{ tunit :: #tunit{}
, shnum :: non_neg_integer()
, offset :: non_neg_integer()
, shstrtab :: #strtab{}
, strtab :: #strtab{}
, symbols :: [#symbol{}]
, symndxmap:: #{string() => pos_integer()}
}).
-spec tunit(#tunit{}, string()) -> ok | {error, {module(), term()}}.
tunit(Tunit, File) ->
emit(layout(Tunit), File).
%% LAYOUT ======================================================================
layout(Tunit) ->
lists:foldl(
fun(Fun, Context) -> Fun(Context) end,
context_new(Tunit),
[ fun process_sections/1
, fun process_symbols/1
, fun order_symbols/1
, fun create_strtab/1
, fun create_symtab/1
, fun create_shstrtab/1
, fun align_shtab/1
, fun encode_relocation_sections/1
]).
context_new(Tunit) ->
#context{ tunit = Tunit
, shnum = 1
, offset = ?ELF36_EHDR_SIZEOF
, shstrtab = strtab_new()
, strtab = strtab_new()
, symbols = []
, symndxmap = #{}
}.
%% Sections --------------------------------------------------------------------
process_sections(Context) ->
#context{tunit = #tunit{sections = Sections}} = Context,
lists:foldl(fun process_section/2, Context, maps:values(Sections)).
process_section(Section, Context) ->
append_section(Context, Section).
append_section(Context, Section) ->
#section{ name = Name
, data = Data
, dot = Dot
, sh_addralign = ShAddrAlign
} = Section,
case Dot of
0 -> Context;
_ ->
#context{ tunit = Tunit
, shnum = ShNum
, offset = Offset
, shstrtab = ShStrTab
} = Context,
{ShName, NewShStrTab} = strtab_enter(ShStrTab, Name),
ShOffset = (Offset + ShAddrAlign - 1) band bnot (ShAddrAlign - 1),
NewSection =
Section#section{sh_name = ShName, sh_offset = ShOffset, shndx = ShNum},
NewTunit = process_relocs(tunit:put_section(Tunit, NewSection), Data),
Context#context{ tunit = NewTunit
, shnum = ShNum + 1
, offset = ShOffset + Dot
, shstrtab = NewShStrTab
}
end.
process_relocs(Tunit, SectionData) ->
case SectionData of
{relocs, _Name, Relocs} ->
lists:foldl(fun process_reloc/2, Tunit, Relocs);
_ -> Tunit
end.
process_reloc(#rela{symbol = Name}, Tunit) ->
case tunit:get_symbol(Tunit, Name) of
#symbol{} -> Tunit;
false ->
Symbol = #symbol{ name = Name
, section = false
, st_value = false
, st_size = false
, st_info = ?ELF_ST_INFO(?STB_GLOBAL, ?STT_NOTYPE)
, st_name = 0
, st_shndx = 0
},
tunit:put_symbol(Tunit, Symbol)
end.
%% Symbols ---------------------------------------------------------------------
process_symbols(Context) ->
#context{tunit = #tunit{symbols = Symbols}} = Context,
lists:foldl(fun process_symbol/2, Context, maps:values(Symbols)).
process_symbol(Symbol, Context) ->
#symbol{name = Name, section = Section} = Symbol,
#context{tunit = Tunit, strtab = StrTab} = Context,
{StName, NewStrTab} = strtab_enter(StrTab, Name),
StShndx =
case Section of
false -> ?SHN_UNDEF;
abs -> ?SHN_ABS;
_ ->
#section{shndx = Shndx} = tunit:get_section(Tunit, Section),
Shndx % assigned in append_section/2 above
end,
NewSymbol = Symbol#symbol{st_name = StName, st_shndx = StShndx},
NewTunit = tunit:put_symbol(Tunit, NewSymbol),
Context#context{tunit = NewTunit, strtab = NewStrTab}.
%% Order symbols ---------------------------------------------------------------
order_symbols(Context) ->
#context{tunit = #tunit{symbols = SymbolsMap}} = Context,
%% FIXME: local symbols first, followed by the weak or global ones
Symbols = maps:values(SymbolsMap),
SymNdxMap = lists:foldl(fun add_symbol_index/2, #{}, Symbols),
%% From this point on we must not use the symbols map in the tunit.
Context#context{symbols = Symbols, symndxmap = SymNdxMap}.
add_symbol_index(#symbol{name = SymName}, SymNdxMap) ->
SymNdx = maps:size(SymNdxMap) + 1,
maps:put(SymName, SymNdx, SymNdxMap).
%% Symbol string table (.strtab) -----------------------------------------------
create_strtab(Context) ->
case Context#context.symbols of
[] -> Context;
[_|_] ->
StrTab = Context#context.strtab,
Image = strtab_image(StrTab),
Section =
#section{ name = ".strtab"
, data = {image, Image}
, dot = image_size(Image)
, shndx = 0 % assigned by append_section/2
, sh_name = 0 % assigned by append_section/2
, sh_type = ?SHT_STRTAB
, sh_offset = 0 % assigned by append_section/2
, sh_flags = ?SHF_MERGE bor ?SHF_STRINGS % FIXME: check
, sh_link = ?SHN_UNDEF
, sh_info = 0
, sh_addralign = 1 % FIXME: check
, sh_entsize = 1 % FIXME: check
},
append_section(Context, Section)
end.
%% Symbol table (.symtab) ------------------------------------------------------
create_symtab(Context) ->
#context{tunit = Tunit, symbols = Symbols} = Context,
case length(Symbols) of
0 -> Context;
NrSyms ->
#section{shndx = StrTabShndx} = tunit:get_section(Tunit, ".strtab"),
Image = symbols_image(Symbols),
Size = (NrSyms + 1) * ?ELF36_SYM_SIZEOF,
Size = image_size(Image), % consistency check
Section =
#section{ name = ".symtab"
, data = {image, Image}
, dot = Size
, shndx = 0 % assigned by append_section/2
, sh_name = 0 % assigned by append_section/2
, sh_type = ?SHT_SYMTAB
, sh_offset = 0 % assigned by append_section/2
, sh_flags = 0
, sh_link = StrTabShndx
, sh_info = 0
, sh_addralign = 4 % FIXME: check
, sh_entsize = ?ELF36_SYM_SIZEOF
},
append_section(Context, Section)
end.
symbols_image(Symbols) ->
ElfSym0 =
#elf36_Sym{ st_name = 0
, st_value = 0
, st_size = 0
, st_info = ?ELF36_ST_INFO(?STB_LOCAL, ?STT_NOTYPE)
, st_other = 0
, st_shndx = ?SHN_UNDEF
},
[elf36_Sym_image(ElfSym0) |
lists:map(fun symbol_image/1, Symbols)].
symbol_image(Symbol) ->
#symbol{ st_value = StValue
, st_size = StSize
, st_info = StInfo
, st_name = StName
, st_shndx = StShndx
} = Symbol,
ElfSym =
#elf36_Sym{ st_name = StName
, st_value = StValue
, st_size = if StSize =:= false -> 0; true -> StSize end
, st_info = StInfo
, st_other = ?STV_DEFAULT % FIXME: should be set earlier
, st_shndx = StShndx
},
elf36_Sym_image(ElfSym).
%% FIXME: the code below belongs in a library
elf36_Sym_image(ElfSym) ->
#elf36_Sym{ st_name = StName
, st_value = StValue
, st_size = StSize
, st_info = StInfo
, st_other = StOther
, st_shndx = StShndx
} = ElfSym,
[ elf36_Word_image(StName)
, elf36_Addr_image(StValue)
, elf36_Word_image(StSize)
, elf36_Uchar_image(StInfo)
, elf36_Uchar_image(StOther)
, elf36_Half_image(StShndx)
].
elf36_Addr_image(Addr) -> uint36_image(Addr).
elf36_Half_image(Half) -> uint18_image(Half).
elf36_Off_image(Off) -> uint36_image(Off).
elf36_Sword_image(Sword) -> uint36_image(Sword band ?PDP10_UINT36_MAX).
elf36_Word_image(Word) -> uint36_image(Word).
elf36_Uchar_image(Uchar) -> uint9_image(Uchar).
uint9_image(Uint9) ->
Uint9 band 511.
uint18_image(Uint18) ->
pdp10_extint:uint18_to_ext(Uint18).
uint36_image(Uint36) ->
pdp10_extint:uint36_to_ext(Uint36).
image_size(Image) -> image_size(Image, 0).
image_size([H | T], Acc) -> image_size(T, image_size(H, Acc));
image_size([], Acc) -> Acc;
image_size(TByte, Acc) when is_integer(TByte), 0 =< TByte, TByte =< 511 -> Acc + 1.
%% Section Header String Table (.shstrtab) -------------------------------------
create_shstrtab(Context) ->
case Context#context.shnum of
1 -> Context;
_ ->
%% Note that append_section/1 enters the section's name to shstrtab,
%% updating its contents if the name wasn't already there, which would
%% invalidate the image recorded in the section. To avoid that, enter
%% the name first.
OldShStrTab = Context#context.shstrtab,
{_ShName, NewShStrTab} = strtab_enter(OldShStrTab, ".shstrtab"),
Image = strtab_image(NewShStrTab),
Section =
#section{ name = ".shstrtab"
, data = {image, Image}
, dot = image_size(Image)
, shndx = 0 % assigned by append_section/2
, sh_name = 0 % assigned by append_section/2
, sh_type = ?SHT_STRTAB
, sh_offset = 0 % assigned by append_section/2
, sh_flags = ?SHF_MERGE bor ?SHF_STRINGS % FIXME: check
, sh_link = ?SHN_UNDEF
, sh_info = 0
, sh_addralign = 1 % FIXME: check
, sh_entsize = 1 % FIXME: check
},
append_section(Context#context{shstrtab = NewShStrTab}, Section)
end.
%% Align Section Header Table --------------------------------------------------
align_shtab(Context) ->
case Context#context.shnum of
1 ->
Context#context{shnum = 0, offset = 0};
_ ->
Offset = Context#context.offset,
ShTabOffset = (Offset + (4 - 1)) band bnot (4 - 1),
Context#context{offset = ShTabOffset}
end.
%% Encode Relocation Sections --------------------------------------------------
encode_relocation_sections(Context) ->
#context{tunit = #tunit{sections = Sections}} = Context,
lists:foldl(fun encode_relocation_section/2, Context, maps:values(Sections)).
encode_relocation_section(Section, Context) ->
case Section of
#section{sh_type = ?SHT_RELA, data = {relocs, Name, Relocs}} ->
Image = relocs_image(Context, Relocs),
#context{tunit = Tunit} = Context,
#section{shndx = ShLink} = tunit:get_section(Tunit, ".symtab"),
#section{shndx = ShInfo} = tunit:get_section(Tunit, Name),
NewSection = Section#section{ data = {image, Image}
, sh_link = ShLink
, sh_info = ShInfo
},
NewTunit = tunit:put_section(Tunit, NewSection),
Context#context{tunit = NewTunit};
_ -> Context
end.
relocs_image(#context{symndxmap = SymNdxMap}, Relocs) ->
lists:map(fun(Reloc) -> reloc_image(SymNdxMap, Reloc) end, Relocs).
reloc_image(SymNdxMap, Rela) ->
#rela{ offset = Offset
, type = Type
, symbol = SymName
, addend = Addend
} = Rela,
SymNdx = maps:get(SymName, SymNdxMap),
Info = ?ELF36_R_INFO(SymNdx, Type),
ElfRela =
#elf36_Rela{ r_offset = Offset
, r_info = Info
, r_addend = Addend
},
elf36_Rela_image(ElfRela).
%% FIXME: the code below belongs in a library
elf36_Rela_image(ElfRela) ->
#elf36_Rela{ r_offset = Offset
, r_info = Info
, r_addend = Addend
} = ElfRela,
[ elf36_Addr_image(Offset)
, elf36_Word_image(Info)
, elf36_Sword_image(Addend)
].
%% String Tables ---------------------------------------------------------------
%% FIXME: duplicates code for .ident directive / .comment section
strtab_new() ->
#strtab{map = maps:new(), dot = 1}. % 1 due to NUL before 1st string
strtab_enter(StrTab = #strtab{map = Map, dot = Dot}, String) ->
case maps:get(String, Map, false) of
false -> {Dot, StrTab#strtab{map = maps:put(String, Dot, Map),
dot = Dot + length(String) + 1}}; % +1 for terminating NUL
Offset -> {Offset, StrTab}
end.
strtab_image(#strtab{map = Map}) ->
KVs = maps:to_list(Map),
VKs = lists:map(fun({K, V}) -> {V, K} end, KVs),
SortedVKs = lists:sort(VKs),
[0 | lists:map(fun({_V, K}) -> K ++ [0] end, SortedVKs)].
%% EMIT ========================================================================
emit(Context, File) ->
case pdp10_stdio:fopen(File, [raw, write, delayed_write]) of
{ok, FP} ->
try
Funs =
[ fun emit_elf_header/3
, fun emit_sections/3
, fun emit_shtab/3
],
emit(Funs, Context, FP, 0)
after pdp10_stdio:fclose(FP)
end;
{error, Reason} -> {error, {?MODULE, {cannot_open, File, Reason}}}
end.
emit([], _Context, _FP, _Offset) -> ok;
emit([Fun | Funs], Context, FP, Offset) ->
case Fun(Context, FP, Offset) of
{ok, NewOffset} -> emit(Funs, Context, FP, NewOffset);
{error, _Reason} = Error -> Error
end.
emit_elf_header(Context, FP, Offset = 0) ->
ShStrTabShndx =
case tunit:get_section(Context#context.tunit, ".shstrtab") of
#section{shndx = Shndx} -> Shndx;
false -> 0
end,
ElfHdr =
#elf36_Ehdr{ e_ident = e_ident()
, e_type = ?ET_REL
, e_machine = ?EM_PDP10 % FIXME: target-specific
, e_version = ?EV_CURRENT
, e_entry = 0
, e_phoff = 0
, e_shoff = Context#context.offset
, e_flags = 0
, e_ehsize = ?ELF36_EHDR_SIZEOF
, e_phentsize = 0
, e_phnum = 0
, e_shentsize = ?ELF36_SHDR_SIZEOF
, e_shnum = Context#context.shnum
, e_shstrndx = ShStrTabShndx
},
emit_image(elf36_Ehdr_image(ElfHdr), ?ELF36_EHDR_SIZEOF, FP, Offset).
emit_sections(Context, FP, Offset = ?ELF36_EHDR_SIZEOF) ->
#context{tunit = #tunit{sections = SectionsMap}} = Context,
Sections = lists:sort(fun order_by_sh_offset/2, maps:values(SectionsMap)),
emit_sections2(Sections, FP, Offset).
emit_sections2([], _FP, Offset) -> {ok, Offset};
emit_sections2([Section | Sections], FP, Offset) ->
case emit_section(Section, FP, Offset) of
{ok, NewOffset} -> emit_sections2(Sections, FP, NewOffset);
{error, _Reason} = Error -> Error
end.
emit_section(Section, FP, Offset) ->
case Section#section.dot of
0 -> {ok, Offset};
Dot ->
ShOffset = Section#section.sh_offset,
NrPadBytes = ShOffset - Offset,
case emit_padding(NrPadBytes, FP) of
ok ->
{image, Image} = Section#section.data,
emit_image(Image, Dot, FP, ShOffset);
{error, _Reason} = Error -> Error
end
end.
emit_shtab(Context, FP, Offset) ->
case Context#context.offset of
0 -> {ok, Offset};
ShTabOffset ->
case emit_padding(ShTabOffset - Offset, FP) of
ok ->
case emit_shdr0(FP, ShTabOffset) of
{ok, NewOffset} ->
#context{tunit = #tunit{sections = SectionsMap}} = Context,
Sections = lists:sort(fun order_by_shndx/2, maps:values(SectionsMap)),
emit_shdrs(Sections, FP, NewOffset);
{error, _Reason} = Error -> Error
end;
{error, _Reason} = Error -> Error
end
end.
emit_shdrs([], _FP, Offset) -> {ok, Offset};
emit_shdrs([Section | Sections], FP, Offset) ->
case emit_shdr(Section, FP, Offset) of
{ok, NewOffset} -> emit_shdrs(Sections, FP, NewOffset);
{error, _Reason} = Error -> Error
end.
emit_shdr(Section, FP, Offset) ->
case Section#section.dot of
0 -> {ok, Offset};
Dot ->
#section{ sh_name = ShName
, sh_type = ShType
, sh_offset = ShOffset
, sh_flags = ShFlags
, sh_link = ShLink
, sh_info = ShInfo
, sh_addralign = ShAddrAlign
, sh_entsize = ShEntSize
} = Section,
ElfShdr =
#elf36_Shdr{ sh_name = ShName
, sh_type = ShType
, sh_flags = ShFlags
, sh_addr = 0
, sh_offset = ShOffset
, sh_size = Dot
, sh_link = ShLink
, sh_info = ShInfo % FIXME: for symtab, LAST_LOCAL + 1
, sh_addralign = ShAddrAlign
, sh_entsize = ShEntSize
},
emit_elf36_Shdr(ElfShdr, FP, Offset)
end.
emit_shdr0(FP, Offset) ->
ElfShdr0 =
#elf36_Shdr{ sh_name = 0
, sh_type = ?SHT_NULL
, sh_flags = 0
, sh_addr = 0
, sh_offset = 0
, sh_size = 0
, sh_link = ?SHN_UNDEF
, sh_info = 0
, sh_addralign = 0
, sh_entsize = 0
},
emit_elf36_Shdr(ElfShdr0, FP, Offset).
emit_elf36_Shdr(Shdr, FP, Offset) ->
emit_image(elf36_Shdr_image(Shdr), ?ELF36_SHDR_SIZEOF, FP, Offset).
elf36_Shdr_image(ElfShdr) ->
#elf36_Shdr{ sh_name = ShName
, sh_type = ShType
, sh_flags = ShFlags
, sh_addr = ShAddr
, sh_offset = ShOffset
, sh_size = ShSize
, sh_link = ShLink
, sh_info = ShInfo
, sh_addralign = ShAddrAlign
, sh_entsize = ShEntSize
} = ElfShdr,
[ elf36_Word_image(ShName)
, elf36_Word_image(ShType)
, elf36_Word_image(ShFlags)
, elf36_Addr_image(ShAddr)
, elf36_Off_image(ShOffset)
, elf36_Word_image(ShSize)
, elf36_Word_image(ShLink)
, elf36_Word_image(ShInfo)
, elf36_Word_image(ShAddrAlign)
, elf36_Word_image(ShEntSize)
].
emit_padding(0, _FP) -> ok;
emit_padding(N, FP) when N > 0 ->
case pdp10_stdio:fputc(0, FP) of
ok -> emit_padding(N - 1, FP);
{error, _Reason} = Error -> Error
end.
order_by_sh_offset(Section1, Section2) ->
Section1#section.sh_offset =< Section2#section.sh_offset.
order_by_shndx(Section1, Section2) ->
Section1#section.shndx =< Section2#section.shndx.
elf36_Ehdr_image(ElfHdr) ->
#elf36_Ehdr{ e_ident = EIdent
, e_type = EType
, e_machine = EMachine
, e_version = EVersion
, e_entry = EEntry
, e_phoff = EPhOff
, e_shoff = EShOff
, e_flags = EFlags
, e_ehsize = EEhSize
, e_phentsize = EPhEntSize
, e_phnum = EPhNum
, e_shentsize = EShEntSize
, e_shnum = EShNum
, e_shstrndx = EShStrNdx
} = ElfHdr,
[ EIdent % already a list of bytes
, elf36_Half_image(EType)
, elf36_Half_image(EMachine)
, elf36_Word_image(EVersion)
, elf36_Addr_image(EEntry)
, elf36_Off_image(EPhOff)
, elf36_Off_image(EShOff)
, elf36_Word_image(EFlags)
, elf36_Half_image(EEhSize)
, elf36_Half_image(EPhEntSize)
, elf36_Half_image(EPhNum)
, elf36_Half_image(EShEntSize)
, elf36_Half_image(EShNum)
, elf36_Half_image(EShStrNdx)
].
e_ident() ->
tuple_to_list(
erlang:make_tuple(
?EI_NIDENT, 0,
[ {1 + ?EI_MAG0, ?ELFMAG0}
, {1 + ?EI_MAG1, ?ELFMAG1}
, {1 + ?EI_MAG2, ?ELFMAG2}
, {1 + ?EI_MAG3, ?ELFMAG3}
, {1 + ?EI_CLASS, ?ELFCLASS36}
, {1 + ?EI_DATA, ?ELFDATA2MSB}
, {1 + ?EI_VERSION, ?EV_CURRENT}
, {1 + ?EI_OSABI, ?ELFOSABI_NONE} % TODO: ELFOSABI_LINUX instead?
, {1 + ?EI_ABIVERSION, 0}
])).
emit_image(Image, NrBytes, FP, Offset) ->
NrBytes = image_size(Image), % assert
case image_write(Image, FP) of
ok -> {ok, Offset + NrBytes};
{error, _Reason} = Error -> Error
end.
image_write([H | T], FP) ->
case image_write(H, FP) of
ok -> image_write(T, FP);
{error, _Reason} = Error -> Error
end;
image_write([], _FP) -> ok;
image_write(TByte, FP) when is_integer(TByte), 0 =< TByte, TByte =< 511 ->
pdp10_stdio:fputc(TByte, FP).
%% Error reporting -------------------------------------------------------------
-spec format_error(term()) -> io_lib:chars().
format_error({cannot_open, File, Reason}) ->
io_lib:format("opening ~s: ~s", [File, error:format(Reason)]).