From a9513554d6e36f050e6114ed63693a977714bbcc Mon Sep 17 00:00:00 2001 From: Mikael Pettersson Date: Tue, 23 Jun 2020 22:00:40 +0200 Subject: [PATCH] sim: sim_loader: add ELF loader --- erlang/apps/sim/src/sim.erl | 6 +- erlang/apps/sim/src/sim_loader.erl | 229 +++++++++++++++++++++++++++++ 2 files changed, 234 insertions(+), 1 deletion(-) create mode 100644 erlang/apps/sim/src/sim_loader.erl diff --git a/erlang/apps/sim/src/sim.erl b/erlang/apps/sim/src/sim.erl index 73a39ee..b5c0aa2 100644 --- a/erlang/apps/sim/src/sim.erl +++ b/erlang/apps/sim/src/sim.erl @@ -144,7 +144,11 @@ version() -> %% Simulation ================================================================== -do_sim(_Options) -> ok. % FIXME +do_sim(#options{exe = Exe, argv = Argv}) -> + case sim_loader:load(Exe, Argv) of + {ok, {_Mem, _PC, _SP, _Argc, _ArgvPtr}} -> ok; % FIXME + {error, _Reason} = Error -> Error + end. %% Error Formatting ============================================================ diff --git a/erlang/apps/sim/src/sim_loader.erl b/erlang/apps/sim/src/sim_loader.erl new file mode 100644 index 0000000..9d6d474 --- /dev/null +++ b/erlang/apps/sim/src/sim_loader.erl @@ -0,0 +1,229 @@ +%%% -*- erlang-indent-level: 2 -*- +%%% +%%% simulator for pdp10-elf +%%% Copyright (C) 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 . +%%% +%%%============================================================================= +%%% +%%% Loads a pdp10-elf user-mode program into a fresh address space. + +-module(sim_loader). + +-export([ load/2 + , format_error/1 + ]). + +-include_lib("lib/include/pdp10_elf36.hrl"). + +-type address() :: non_neg_integer(). + +-export_type([address/0]). + +%% ELF loader ================================================================== + +-spec load(file:name_all(), [string()]) + -> {ok, { sim_mem:mem() + , PC :: address() + , SP :: address() + , Argc :: non_neg_integer() + , Argv :: address()}} + | {error, {module(), term()}}. +load(Exe, ArgvStrings) -> + case pdp10_stdio:fopen(Exe, [raw, read]) of + {ok, FP} -> + try load_fp(FP, ArgvStrings) + after pdp10_stdio:fclose(FP) end; + {error, _Reason} = Error -> Error + end. + +load_fp(FP, ArgvStrings) -> + case pdp10_elf36:read_Ehdr(FP) of + {ok, Ehdr} -> load(FP, Ehdr, ArgvStrings); + {error, _Reason} = Error -> Error + end. + +load(FP, Ehdr, ArgvStrings) -> + case Ehdr#elf36_Ehdr.e_type of + ?ET_EXEC -> + case pdp10_elf36:read_PhTab(FP, Ehdr) of + {ok, PhTab} -> load(FP, Ehdr, PhTab, ArgvStrings); + {error, _Reason} = Error -> Error + end; + EType -> {error, {?MODULE, {invalid_ehdr_type, EType}}} + end. + +load(FP, Ehdr, PhTab, ArgvStrings) -> + Mem = sim_mem:new(), + case load_phtab(FP, Ehdr, PhTab, 0, Mem) of + {ok, PC} -> + {SP, Argv} = init_stack(Mem, ArgvStrings), + {ok, {Mem, PC, SP, _Argc = length(ArgvStrings), Argv}}; + {error, _Reason} = Error -> + sim_mem:delete(Mem), + Error + end. + +init_stack(Mem, ArgvStrings) -> + %% TODO: assumes large or small code model output, not tiny + Stack0 = 8#00000001000 bsl 2, % section 0, page 1, word 0 + Size = ((512 - 2) * 512) bsl 2, % 510 pages (pages 0 and 511 left unmapped) + map_zero_core(Mem, Stack0, Size), + {ArgvPointers, Stack1} = store_strings(ArgvStrings, Mem, Stack0), + %% Stack1 is the start of argv[]. + Stack2 = store_pointers(ArgvPointers, Mem, Stack1), + %% Stack2 now points after argv[] to a word containing a zero return address. + { _SP = byte_address_to_global_word_address(Stack2) + , _Argv = byte_address_to_global_word_address(Stack1) + }. + +store_strings(ArgvStrings, Mem, Stack) -> + store_strings(ArgvStrings, Mem, Stack, []). + +store_strings([], _Mem, Stack, Acc) -> {lists:reverse(Acc), Stack}; +store_strings([String | Strings], Mem, Stack, Acc) -> + Pointer = byte_address_to_global_byte_pointer(Stack), + NewStack = store_string(String, Mem, Stack), + store_strings(Strings, Mem, NewStack, [Pointer | Acc]). + +store_string([B0, B1, B2, B3 | Rest], Mem, Stack) -> + Word = pdp10_extint:uint36_from_ext([B0, B1, B2, B3]), + write_word(Mem, Stack, Word), + store_string(Rest, Mem, Stack + 4); +store_string(Tail, Mem, Stack) -> + Pad = lists:duplicate(0, 4 - length(Tail)), + Word = pdp10_extint:uint36_from_ext(Tail ++ Pad), + write_word(Mem, Stack, Word), + Stack + 4. + +store_pointers([], _Mem, Stack) -> Stack; +store_pointers([Pointer | Pointers], Mem, Stack) -> + write_word(Mem, Stack, Pointer), + store_pointers(Pointers, Mem, Stack + 4). + +byte_address_to_global_word_address(ByteAddress) -> + ByteAddress bsr 2. % EFIW: bits 0 to 5 zero, address in bits 6 to 35. + +byte_address_to_global_byte_pointer(ByteAddress) -> + Y = ByteAddress bsr 2, + PS = 8#70 + (ByteAddress band 3), + (PS bsl 30) bor Y. + +load_phtab(_FP, Ehdr, _PhTab = [], _PhdrIx, _Mem) -> {ok, Ehdr#elf36_Ehdr.e_entry}; +load_phtab(FP, Ehdr, [Phdr | PhTab], PhdrIx, Mem) -> + case load_phdr(FP, Phdr, PhdrIx, Mem) of + ok -> load_phtab(FP, Ehdr, PhTab, PhdrIx + 1, Mem); + {error, _Reason} = Error -> Error + end. + +load_phdr(FP, Phdr, PhdrIx, Mem) -> + case Phdr#elf36_Phdr.p_type of + ?PT_NULL -> ok; + ?PT_LOAD -> + case is_valid_phdr(Phdr) of + true -> do_load_phdr(FP, Phdr, Mem); + false -> {error, {?MODULE, {invalid_phdr, PhdrIx}}} + end; + PType -> {error, {?MODULE, {invalid_phdr_type, PType}}} + end. + +do_load_phdr(FP, Phdr, Mem) -> + #elf36_Phdr{ p_offset = Offset + , p_vaddr = VAddr + , p_filesz = FileSz + , p_memsz = MemSz + , p_flags = Flags } = Phdr, + %% TODO: change this use demand paging to read the file lazily + case pdp10_stdio:fseek(FP, {bof, Offset}) of + ok -> + map_zero_core(Mem, VAddr, MemSz), + 0 = (VAddr band 3), + case copy_file_to_core(Mem, VAddr, FP, FileSz) of + ok -> + case (Flags band ?PF_W) of + 0 -> remap_read_only(Mem, VAddr, MemSz); + _ -> ok + end; + {error, _Reason} = Error -> Error + end; + {error, _Reason} = Error -> Error + end. + +map_zero_core(Mem, VAddr, Size) when Size > 0 -> + PFN = VAddr bsr (9 + 2), + false = sim_mem:mquery(Mem, PFN), + sim_mem:mmap(Mem, PFN, 4+2, core), + map_zero_core(Mem, VAddr + 512*4, Size - 512*4); +map_zero_core(_Mem, _VAddr, _Size) -> ok. + +copy_file_to_core(Mem, VAddr, FP, Size) when Size >= 4 -> + case read(FP, 4) of + {ok, Nonets} -> + Word = pdp10_extint:uint36_from_ext(Nonets), + write_word(Mem, VAddr, Word), + copy_file_to_core(Mem, VAddr + 4, FP, Size - 4); + {error, _Reason} = Error -> Error + end; +copy_file_to_core(Mem, VAddr, FP, Size) when Size > 0 -> + case read(FP, Size) of + {ok, Nonets} -> + Word = pdp10_extint:uint36_from_ext(Nonets ++ lists:duplicate(4 - Size, 0)), + write_word(Mem, VAddr, Word); + {error, _Reason} = Error -> Error + end; +copy_file_to_core(_Mem, _VAddr, _FP, _Size = 0) -> ok. + +read(FP, N) -> + case pdp10_stdio:fread(1, N, FP) of + eof -> {error, {?MODULE, eof}}; + Else -> Else % {ok, Nonets} or {error, Reason} + end. + +remap_read_only(Mem, VAddr, Size) when Size > 0 -> + PFN = VAddr bsr (9 + 2), + ok = sim_mem:mprotect(Mem, PFN), + remap_read_only(Mem, VAddr + 512*4, Size - 512*4); +remap_read_only(_Mem, _VAddr, _Size) -> ok. + +is_valid_phdr(Phdr) -> + #elf36_Phdr{ p_offset = Offset + , p_vaddr = VAddr + , p_filesz = FileSz + , p_memsz = MemSz + , p_flags = Flags } = Phdr, + is_page_aligned(Offset) andalso + is_page_aligned(VAddr) andalso + MemSz >= FileSz andalso + no_excess_flags(Flags). + +is_page_aligned(Offset) -> (Offset band (512*4 - 1)) =:= 0. + +no_excess_flags(Flags) -> (Flags band 8#7) =:= Flags. + +write_word(Mem, ByteAddress, Word) -> + ok = sim_mem:write_word(Mem, ByteAddress bsr 2, Word). + +%% Error Formatting ============================================================ + +-spec format_error(term()) -> io_lib:chars(). +format_error(Reason) -> + case Reason of + {invalid_ehdr_type, Type} -> io_lib:format("invalid Ehdr.e_type ~p", [Type]); + {invalid_phdr, Ix} -> io_lib:format("invalid phdr at index ~p", [Ix]); + {invalid_phdr_type, Type} -> io_lib:format("invalid Phdr.p_type ~p", [Type]); + eof -> "premature EOF while reading ELF segment" + end.