diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 0000000..fa7adc7 --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +3.3.5 diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..e69de29 diff --git a/elna.cabal b/elna.cabal index 136cd43..d18a628 100644 --- a/elna.cabal +++ b/elna.cabal @@ -17,7 +17,9 @@ extra-doc-files: TODO README common warnings build-depends: base >=4.7 && <5, + bytestring ^>= 0.12.1, megaparsec ^>= 9.6, + vector ^>= 0.13.1, text ^>= 2.0 ghc-options: -Wall default-extensions: @@ -25,6 +27,7 @@ common warnings ExplicitForAll, LambdaCase, OverloadedStrings, + DuplicateRecordFields, RecordWildCards default-language: GHC2021 @@ -36,6 +39,7 @@ library elna-internal Language.Elna.Intermediate Language.Elna.Location Language.Elna.NameAnalysis + Language.Elna.Object.Elf Language.Elna.Parser Language.Elna.SymbolTable Language.Elna.TypeAnalysis @@ -45,7 +49,6 @@ library elna-internal hashable ^>= 1.4.3, parser-combinators ^>= 1.3, transformers ^>= 0.6.1, - vector ^>= 0.13.1, unordered-containers ^>= 0.2.20 hs-source-dirs: lib diff --git a/lib/Language/Elna/Object/Elf.hs b/lib/Language/Elna/Object/Elf.hs new file mode 100644 index 0000000..1a4bcc8 --- /dev/null +++ b/lib/Language/Elna/Object/Elf.hs @@ -0,0 +1,464 @@ +module Language.Elna.Object.Elf + ( ByteOrder(..) + , Elf32_Addr + , Elf32_Off + , Elf32_Half + , Elf32_Word + , Elf32_Sword + , Elf32_Ehdr(..) + , Elf32_Rel(..) + , Elf32_Rela(..) + , Elf32_Shdr(..) + , Elf32_Sym(..) + , ElfEncodingError(..) + , ElfIdentification(..) + , ElfMachine(..) + , ElfVersion(..) + , ElfClass(..) + , ElfData(..) + , ElfType(..) + , ElfSectionType(..) + , ElfSymbolBinding(..) + , ElfSymbolType(..) + , elf32Addr + , elf32Half + , elf32Off + , elf32Shdr + , elf32Sword + , elf32Word + , elf32Ehdr + , elf32Rel + , elf32Rela + , elf32Sym + , elfIdentification + , rInfo + , stInfo + ) where + +import Control.Exception (Exception(..)) +import Data.Bits (Bits(..)) +import qualified Data.ByteString.Builder as ByteString.Builder +import Data.Int (Int32) +import Data.Word (Word8, Word16, Word32) +import qualified Data.ByteString as ByteString + +-- * Data types. + +type Elf32_Addr = Word32 -- ^ Unsigned program address. +type Elf32_Half = Word16 -- ^ Unsigned medium integer. +type Elf32_Off = Word32 -- ^ Unsigned file offset. +type Elf32_Sword = Int32 -- ^ Signed large integer. +type Elf32_Word = Word32 -- ^ Unsigned large integer. + +data ElfClass + = ELFCLASSNONE -- ^ Invalid class. + | ELFCLASS32 -- ^ 32-bit objects. + | ELFCLASS64 -- ^ 64-bit objects. + deriving Eq + +instance Show ElfClass + where + show ELFCLASSNONE = "ELFCLASSNONE" + show ELFCLASS32 = "ELFCLASS32" + show ELFCLASS64 = "ELFCLASS64" + +instance Enum ElfClass + where + toEnum 0 = ELFCLASSNONE + toEnum 1 = ELFCLASS32 + toEnum 2 = ELFCLASS64 + toEnum _ = error "Unknown Elf class" + fromEnum ELFCLASSNONE = 0 + fromEnum ELFCLASS32 = 1 + fromEnum ELFCLASS64 = 1 + +-- | Data encoding. +data ElfData + = ELFDATANONE + | ELFDATA2LSB + | ELFDATA2MSB + deriving Eq + +instance Show ElfData + where + show ELFDATANONE = "ELFDATANONE" + show ELFDATA2LSB = "ELFDATA2LSB" + show ELFDATA2MSB = "ELFDATA2MSB" + +instance Enum ElfData + where + toEnum 0 = ELFDATANONE + toEnum 1 = ELFDATA2LSB + toEnum 2 = ELFDATA2MSB + toEnum _ = error "Unknown elf data" + fromEnum ELFDATANONE = 0 + fromEnum ELFDATA2LSB = 1 + fromEnum ELFDATA2MSB = 2 + +data ElfIdentification = ElfIdentification ElfClass ElfData + deriving Eq + +-- | ELF header. +data Elf32_Ehdr = Elf32_Ehdr + { e_ident :: ElfIdentification + , e_type :: ElfType + , e_machine :: ElfMachine + , e_version :: ElfVersion + , e_entry :: Elf32_Addr + , e_phoff :: Elf32_Off + , e_shoff :: Elf32_Off + , e_flags :: Elf32_Word + , e_ehsize :: Elf32_Half + , e_phentsize :: Elf32_Half + , e_phnum :: Elf32_Half + , e_shentsize :: Elf32_Half + , e_shnum :: Elf32_Half + , e_shstrndx :: Elf32_Half + } deriving Eq + +-- | Section header. +data Elf32_Shdr = Elf32_Shdr + { sh_name :: Elf32_Word + , sh_type :: ElfSectionType + , sh_flags :: Elf32_Word + , sh_addr :: Elf32_Addr + , sh_offset :: Elf32_Off + , sh_size :: Elf32_Word + , sh_link :: Elf32_Word + , sh_info :: Elf32_Word + , sh_addralign :: Elf32_Word + , sh_entsize :: Elf32_Word + } deriving Eq + +data ElfMachine + = ElfMachine Elf32_Half + | EM_NONE -- ^ No machine. + | EM_M32 -- ^ AT&T WE 32100. + | EM_SPARC -- ^ SPARC. + | EM_386 -- ^ Intel Architecture. + | EM_68K -- ^ Motorola 68000. + | EM_88K -- ^ Motorola 88000. + | EM_860 -- ^ Intel 80860. + | EM_MIPS -- ^ MIPS RS3000 Big-Endian. + | EM_MIPS_RS4_BE -- ^ MIPS RS4000 Big-Endian. + | EM_RISCV -- ^ RISC-V. + deriving Eq + +instance Enum ElfMachine + where + toEnum 0x0 = EM_NONE + toEnum 0x1 = EM_M32 + toEnum 0x2 = EM_SPARC + toEnum 0x3 = EM_386 + toEnum 0x4 = EM_68K + toEnum 0x5 = EM_88K + toEnum 0x7 = EM_860 + toEnum 0x8 = EM_MIPS + toEnum 0xa = EM_MIPS_RS4_BE + toEnum 0xf3 = EM_RISCV + toEnum x = ElfMachine $ fromIntegral x + fromEnum EM_NONE = 0x0 + fromEnum EM_M32 = 0x1 + fromEnum EM_SPARC = 0x2 + fromEnum EM_386 = 0x3 + fromEnum EM_68K = 0x4 + fromEnum EM_88K = 0x5 + fromEnum EM_860 = 0x7 + fromEnum EM_MIPS = 0x8 + fromEnum EM_MIPS_RS4_BE = 0xa + fromEnum EM_RISCV = 0xf3 + fromEnum (ElfMachine x) = fromIntegral x + +data ElfVersion + = ElfVersion Elf32_Word + | EV_NONE -- ^ Invalid versionn. + | EV_CURRENT -- ^ Current version. + deriving Eq + +instance Enum ElfVersion + where + toEnum 0 = EV_NONE + toEnum 1 = EV_CURRENT + toEnum x = ElfVersion $ fromIntegral x + fromEnum EV_NONE = 0 + fromEnum EV_CURRENT = 1 + fromEnum (ElfVersion x) = fromIntegral x + +data ElfType + = ElfType Elf32_Half + | ET_NONE -- ^ No file type. + | ET_REL -- ^ Relocatable file. + | ET_EXEC -- ^ Executable file. + | ET_DYN -- ^ Shared object file. + | ET_CORE -- ^ Core file. + | ET_LOPROC -- ^ Processor-specific. + | ET_HIPROC -- ^ Processor-specific. + deriving Eq + +instance Enum ElfType + where + toEnum 0 = ET_NONE + toEnum 1 = ET_REL + toEnum 2 = ET_EXEC + toEnum 3 = ET_DYN + toEnum 4 = ET_CORE + toEnum 0xff00 = ET_LOPROC + toEnum 0xffff = ET_HIPROC + toEnum x = ElfType $ fromIntegral x + fromEnum ET_NONE = 0 + fromEnum ET_REL = 1 + fromEnum ET_EXEC = 2 + fromEnum ET_DYN = 3 + fromEnum ET_CORE = 4 + fromEnum ET_LOPROC = 0xff00 + fromEnum ET_HIPROC = 0xffff + fromEnum (ElfType x) = fromIntegral x + +data Elf32_Sym = Elf32_Sym + { st_name :: Elf32_Word + , st_value :: Elf32_Addr + , st_size :: Elf32_Word + , st_info :: Word8 + , st_other :: Word8 + , st_shndx :: Elf32_Half + } deriving Eq + +data ElfSymbolBinding + = ElfSymbolBinding Word8 + | STB_LOCAL + | STB_GLOBAL + | STB_WEAK + | STB_LOPROC + | STB_HIPROC + deriving Eq + +instance Enum ElfSymbolBinding + where + toEnum 0 = STB_LOCAL + toEnum 1 = STB_GLOBAL + toEnum 2 = STB_WEAK + toEnum 13 = STB_LOPROC + toEnum 15 = STB_HIPROC + toEnum x = ElfSymbolBinding $ fromIntegral x + fromEnum STB_LOCAL = 0 + fromEnum STB_GLOBAL = 1 + fromEnum STB_WEAK = 2 + fromEnum STB_LOPROC = 13 + fromEnum STB_HIPROC = 15 + fromEnum (ElfSymbolBinding x) = fromIntegral x + +data ElfSymbolType + = ElfSymbolType Word8 + | STT_NOTYPE + | STT_OBJECT + | STT_FUNC + | STT_SECTION + | STT_FILE + | STT_LOPROC + | STT_HIPROC + deriving Eq + +instance Enum ElfSymbolType + where + toEnum 0 = STT_NOTYPE + toEnum 1 = STT_OBJECT + toEnum 2 = STT_FUNC + toEnum 3 = STT_SECTION + toEnum 4 = STT_FILE + toEnum 13 = STT_LOPROC + toEnum 15 = STT_HIPROC + toEnum x = ElfSymbolType $ fromIntegral x + fromEnum STT_NOTYPE = 0 + fromEnum STT_OBJECT = 1 + fromEnum STT_FUNC = 2 + fromEnum STT_SECTION = 3 + fromEnum STT_FILE = 4 + fromEnum STT_LOPROC = 13 + fromEnum STT_HIPROC = 15 + fromEnum (ElfSymbolType x) = fromIntegral x + +data Elf32_Rel = Elf32_Rel + { r_offset :: Elf32_Addr + , r_info :: Elf32_Word + } deriving Eq + +data Elf32_Rela = Elf32_Rela + { r_offset :: Elf32_Addr + , r_info :: Elf32_Word + , r_addend :: Elf32_Sword + } deriving Eq + +data ElfSectionType + = ElfSectionType Elf32_Word + | SHT_NULL + | SHT_PROGBITS + | SHT_SYMTAB + | SHT_STRTAB + | SHT_RELA + | SHT_HASH + | SHT_DYNAMIC + | SHT_NOTE + | SHT_NOBITS + | SHT_REL + | SHT_SHLIB + | SHT_DYNSYM + | SHT_LOPROC + | SHT_HIPROC + | SHT_LOUSER + | SHT_HIUSER + deriving Eq + +instance Enum ElfSectionType + where + toEnum 0 = SHT_NULL + toEnum 1 = SHT_PROGBITS + toEnum 2 = SHT_SYMTAB + toEnum 3 = SHT_STRTAB + toEnum 4 = SHT_RELA + toEnum 5 = SHT_HASH + toEnum 6 = SHT_DYNAMIC + toEnum 7 = SHT_NOTE + toEnum 8 = SHT_NOBITS + toEnum 9 = SHT_REL + toEnum 10 = SHT_SHLIB + toEnum 11 = SHT_DYNSYM + toEnum 0x70000000 = SHT_LOPROC + toEnum 0x7fffffff = SHT_HIPROC + toEnum 0x80000000 = SHT_LOUSER + toEnum 0xffffffff = SHT_HIUSER + toEnum x = ElfSectionType $ fromIntegral x + fromEnum SHT_NULL = 0 + fromEnum SHT_PROGBITS = 1 + fromEnum SHT_SYMTAB = 2 + fromEnum SHT_STRTAB = 3 + fromEnum SHT_RELA = 4 + fromEnum SHT_HASH = 5 + fromEnum SHT_DYNAMIC = 6 + fromEnum SHT_NOTE = 7 + fromEnum SHT_NOBITS = 8 + fromEnum SHT_REL = 9 + fromEnum SHT_SHLIB = 10 + fromEnum SHT_DYNSYM = 11 + fromEnum SHT_LOPROC = 0x70000000 + fromEnum SHT_HIPROC = 0x7fffffff + fromEnum SHT_LOUSER = 0x80000000 + fromEnum SHT_HIUSER = 0xffffffff + fromEnum (ElfSectionType x) = fromIntegral x + +-- * Help types and functions. + +data ByteOrder = LSB | MSB + deriving Eq + +data ElfEncodingError + = ElfInvalidByteOrderError + | ElfUnsupportedClassError ElfClass + deriving Eq + +instance Show ElfEncodingError + where + show ElfInvalidByteOrderError = "Invalid byte order." + show (ElfUnsupportedClassError class') = + concat ["Elf class \"", show class', "\" is not supported."] + +instance Exception ElfEncodingError + +fromIntegralEnum :: (Enum a, Num b) => a -> b +fromIntegralEnum = fromIntegral . fromEnum + +-- * Encoding functions. + +elf32Addr :: ByteOrder -> Elf32_Addr -> ByteString.Builder.Builder +elf32Addr LSB = ByteString.Builder.word32LE +elf32Addr MSB = ByteString.Builder.word32BE + +elf32Half :: ByteOrder -> Elf32_Half -> ByteString.Builder.Builder +elf32Half LSB = ByteString.Builder.word16LE +elf32Half MSB = ByteString.Builder.word16BE + +elf32Off :: ByteOrder -> Elf32_Off -> ByteString.Builder.Builder +elf32Off LSB = ByteString.Builder.word32LE +elf32Off MSB = ByteString.Builder.word32BE + +elf32Sword :: ByteOrder -> Elf32_Sword -> ByteString.Builder.Builder +elf32Sword LSB = ByteString.Builder.int32LE +elf32Sword MSB = ByteString.Builder.int32BE + +elf32Word :: ByteOrder -> Elf32_Word -> ByteString.Builder.Builder +elf32Word LSB = ByteString.Builder.word32LE +elf32Word MSB = ByteString.Builder.word32BE + +elfIdentification :: ElfIdentification -> ByteString.Builder.Builder +elfIdentification (ElfIdentification elfClass elfData) + = ByteString.Builder.word8 0x7f + <> ByteString.Builder.string7 "ELF" + <> ByteString.Builder.word8 (fromIntegralEnum elfClass) + <> ByteString.Builder.word8 (fromIntegralEnum elfData) + <> ByteString.Builder.word8 (fromIntegralEnum EV_CURRENT) + <> ByteString.Builder.byteString (ByteString.replicate 9 0) + +elf32Ehdr :: Elf32_Ehdr -> Either ElfEncodingError ByteString.Builder.Builder +elf32Ehdr Elf32_Ehdr{..} = encode <$> byteOrder + where + encode byteOrder' + = elfIdentification e_ident + <> elf32Half byteOrder' (fromIntegralEnum e_type) + <> elf32Half byteOrder' (fromIntegralEnum e_machine) + <> elf32Word byteOrder' (fromIntegralEnum e_version) + <> elf32Addr byteOrder' e_entry + <> elf32Off byteOrder' e_phoff + <> elf32Off byteOrder' e_shoff + <> elf32Word byteOrder' e_flags + <> elf32Half byteOrder' e_ehsize + <> elf32Half byteOrder' e_phentsize + <> elf32Half byteOrder' e_phnum + <> elf32Half byteOrder' e_shentsize + <> elf32Half byteOrder' e_shnum + <> elf32Half byteOrder' e_shstrndx + byteOrder + | ElfIdentification class' _ <- e_ident + , class' /= ELFCLASS32 = Left $ ElfUnsupportedClassError class' + | ElfIdentification _ ELFDATA2MSB <- e_ident = Right MSB + | ElfIdentification _ ELFDATA2LSB <- e_ident = Right LSB + | ElfIdentification _ ELFDATANONE <- e_ident = Left ElfInvalidByteOrderError + +elf32Shdr :: ByteOrder -> Elf32_Shdr -> ByteString.Builder.Builder +elf32Shdr byteOrder Elf32_Shdr{..} + = elf32Word byteOrder sh_name + <> elf32Word byteOrder (fromIntegralEnum sh_type) + <> elf32Word byteOrder sh_flags + <> elf32Addr byteOrder sh_addr + <> elf32Off byteOrder sh_offset + <> elf32Word byteOrder sh_size + <> elf32Word byteOrder sh_link + <> elf32Word byteOrder sh_info + <> elf32Word byteOrder sh_addralign + <> elf32Word byteOrder sh_entsize + +elf32Sym :: ByteOrder -> Elf32_Sym -> ByteString.Builder.Builder +elf32Sym byteOrder Elf32_Sym{..} + = elf32Word byteOrder st_name + <> elf32Addr byteOrder st_value + <> elf32Word byteOrder st_size + <> ByteString.Builder.word8 st_info + <> ByteString.Builder.word8 st_other + <> elf32Half byteOrder st_shndx + +elf32Rel :: ByteOrder -> Elf32_Rel -> ByteString.Builder.Builder +elf32Rel byteOrder Elf32_Rel{..} + = elf32Addr byteOrder r_offset + <> elf32Word byteOrder r_info + +elf32Rela :: ByteOrder -> Elf32_Rela -> ByteString.Builder.Builder +elf32Rela byteOrder Elf32_Rela{..} + = elf32Addr byteOrder r_offset + <> elf32Word byteOrder r_info + <> elf32Sword byteOrder r_addend + +stInfo :: ElfSymbolBinding -> ElfSymbolType -> Word8 +stInfo binding type' = fromIntegralEnum binding `shiftL` 4 + .|. (fromIntegralEnum type' .&. 0xf) + +rInfo :: Elf32_Word -> Word8 -> Elf32_Word +rInfo symbol type' = symbol `shiftL` 8 + .|. fromIntegralEnum type' diff --git a/rakelib/cross.rake b/rakelib/cross.rake new file mode 100644 index 0000000..3de7d79 --- /dev/null +++ b/rakelib/cross.rake @@ -0,0 +1,307 @@ +require 'pathname' +require 'uri' +require 'net/http' +require 'rake/clean' +require 'open3' +require 'etc' + +GCC_VERSION = "14.2.0" +BINUTILS_VERSION = '2.43.1' +GLIBC_VERSION = '2.40' +KERNEL_VERSION = '5.15.166' + +TMP = Pathname.new('./build') + +CLEAN.include TMP + +class BuildTarget + attr_accessor(:build, :gcc, :target, :tmp) + + def gxx + @gcc.gsub 'c', '+' + end + + def sysroot + tmp + 'sysroot' + end + + def rootfs + tmp + 'rootfs' + end + + def tools + tmp + 'tools' + end +end + +def gcc_verbose(gcc_binary) + read, write = IO.pipe + sh({'LANG' => 'C'}, gcc_binary, '--verbose', err: write) + write.close + output = read.read + read.close + output +end + +def find_build_target(gcc_version, task) + gcc_binary = 'gcc' + output = gcc_verbose gcc_binary + + if output.start_with? 'Apple clang' + gcc_binary = "gcc-#{gcc_version.split('.').first}" + output = gcc_verbose gcc_binary + end + result = output + .lines + .each_with_object(BuildTarget.new) do |line, accumulator| + if line.start_with? 'Target: ' + accumulator.build = line.split(' ').last.strip + elsif line.start_with? 'COLLECT_GCC' + accumulator.gcc = line.split('=').last.strip + end + end + result.tmp = TMP + task.with_defaults target: 'riscv32-unknown-linux-gnu' + result.target = task[:target] + result +end + +def download_and_unarchive(url, target) + case File.extname url.path + when '.bz2' + archive_type = '-j' + root_directory = File.basename url.path, '.tar.bz2' + when '.xz' + archive_type = '-J' + root_directory = File.basename url.path, '.tar.xz' + else + raise "Unsupported archive type #{url.path}." + end + + Net::HTTP.start(url.host, url.port, use_ssl: url.scheme == 'https') do |http| + request = Net::HTTP::Get.new url.request_uri + + http.request request do |response| + case response + when Net::HTTPRedirection + download_and_unarchive URI.parse(response['location']) + when Net::HTTPSuccess + Open3.popen2 'tar', '-C', target.to_path, archive_type, '-xv' do |stdin, stdout, wait_thread| + stdout.close + + response.read_body do |chunk| + stdin.write chunk + end + end + else + response.error! + end + end + end + target + root_directory +end + +namespace :cross do + desc 'Build cross binutils' + task :binutils, [:target] do |_, args| + options = find_build_target GCC_VERSION, args + options.tools.mkpath + source_directory = download_and_unarchive( + URI.parse("https://ftp.gnu.org/gnu/binutils/binutils-#{BINUTILS_VERSION}.tar.xz"), + options.tools) + + cwd = source_directory.dirname + 'build-binutils' + cwd.mkpath + options.rootfs.mkpath + + env = { + 'CC' => options.gcc, + 'CXX' => options.gxx + } + configure_options = [ + "--prefix=#{options.rootfs.realpath}", + "--target=#{options.target}", + '--disable-nls', + '--enable-gprofng=no', + '--disable-werror', + '--enable-default-hash-style=gnu', + '--disable-libquadmath' + ] + configure = source_directory.relative_path_from(cwd) + 'configure' + sh env, configure.to_path, *configure_options, chdir: cwd.to_path + sh env, 'make', '-j', Etc.nprocessors.to_s, chdir: cwd.to_path + sh env, 'make', 'install', chdir: cwd.to_path + end + + desc 'Build stage 1 GCC' + task :gcc1, [:target] do |_, args| + options = find_build_target GCC_VERSION, args + options.tools.mkpath + source_directory = download_and_unarchive( + URI.parse("https://gcc.gnu.org/pub/gcc/releases/gcc-#{GCC_VERSION}/gcc-#{GCC_VERSION}.tar.xz"), + options.tools) + + cwd = source_directory.dirname + 'build-gcc' + cwd.mkpath + options.rootfs.mkpath + options.sysroot.mkpath + + sh 'contrib/download_prerequisites', chdir: source_directory.to_path + configure_options = [ + "--prefix=#{options.rootfs.realpath}", + "--with-sysroot=#{options.sysroot.realpath}", + '--enable-languages=c,c++', + '--disable-shared', + '--with-arch=rv32imafdc', + '--with-abi=ilp32d', + '--with-tune=rocket', + '--with-isa-spec=20191213', + '--disable-bootstrap', + '--disable-multilib', + '--disable-libmudflap', + '--disable-libssp', + '--disable-libquadmath', + '--disable-libsanitizer', + '--disable-threads', + '--disable-libatomic', + '--disable-libgomp', + '--disable-libvtv', + '--disable-libstdcxx', + '--disable-nls', + '--with-newlib', + '--without-headers', + "--target=#{options.target}", + "--build=#{options.build}", + "--host=#{options.build}" + ] + flags = '-O2 -fPIC' + env = { + 'CC' => options.gcc, + 'CXX' => options.gxx, + 'CFLAGS' => flags, + 'CXXFLAGS' => flags, + 'PATH' => "#{options.rootfs.realpath + 'bin'}:#{ENV['PATH']}" + } + configure = source_directory.relative_path_from(cwd) + 'configure' + sh env, configure.to_path, *configure_options, chdir: cwd.to_path + sh env, 'make', '-j', Etc.nprocessors.to_s, chdir: cwd.to_path + sh env, 'make', 'install', chdir: cwd.to_path + end + + desc 'Copy glibc headers' + task :headers, [:target] do |_, args| + options = find_build_target GCC_VERSION, args + options.tools.mkpath + + source_directory = download_and_unarchive( + URI.parse("https://ftp.gnu.org/gnu/glibc/glibc-#{GLIBC_VERSION}.tar.xz"), + options.tools) + include_directory = options.tools + 'include' + + include_directory.mkpath + cp (source_directory + 'elf/elf.h'), (include_directory + 'elf.h') + end + + desc 'Build linux kernel' + task :kernel, [:target] do |_, args| + options = find_build_target GCC_VERSION, args + options.tools.mkpath + + cwd = download_and_unarchive( + URI.parse("https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-#{KERNEL_VERSION}.tar.xz"), + options.tools) + + env = { + 'CROSS_COMPILE' => "#{options.target}-", + 'ARCH' => 'riscv', + 'PATH' => "#{options.rootfs.realpath + 'bin'}:#{ENV['PATH']}", + 'HOSTCFLAGS' => "-D_UUID_T -D__GETHOSTUUID_H -I#{options.tools.realpath + 'include'}" + } + sh env, 'make', 'rv32_defconfig', chdir: cwd.to_path + sh env, 'make', '-j', Etc.nprocessors.to_s, chdir: cwd.to_path + sh env, 'make', 'headers', chdir: cwd.to_path + + user_directory = options.sysroot + 'usr' + + user_directory.mkpath + cp_r (cwd + 'usr/include'), (user_directory + 'include') + end + + desc 'Build glibc' + task :glibc, [:target] do |_, args| + options = find_build_target GCC_VERSION, args + source_directory = options.tools + "glibc-#{GLIBC_VERSION}" + configure_options = [ + '--prefix=/usr', + "--host=#{options.target}", + "--target=#{options.target}", + "--build=#{options.build}", + "--enable-kernel=#{KERNEL_VERSION}", + "--with-headers=#{options.sysroot.realpath + 'usr/include'}", + '--disable-nscd', + '--disable-libquadmath', + '--disable-libitm', + '--disable-werror', + 'libc_cv_forced_unwind=yes' + ] + bin = options.rootfs.realpath + 'bin' + env = { + 'PATH' => "#{bin}:#{ENV['PATH']}", + 'MAKE' => 'make' # Otherwise it uses gnumake which can be different and too old. + } + cwd = source_directory.dirname + 'build-glibc' + cwd.mkpath + + configure = source_directory.relative_path_from(cwd) +'./configure' + sh env, configure.to_path, *configure_options, chdir: cwd.to_path + sh env, 'make', '-j', Etc.nprocessors.to_s, chdir: cwd.to_path + sh env, 'make', "install_root=#{options.sysroot.realpath}", 'install', chdir: cwd.to_path + end + + desc 'Build stage 2 GCC' + task :gcc2, [:target] do |_, args| + options = find_build_target GCC_VERSION, args + source_directory = options.tools + "gcc-#{GCC_VERSION}" + cwd = options.tools + 'build-gcc' + + rm_rf cwd + cwd.mkpath + + configure_options = [ + "--prefix=#{options.rootfs.realpath}", + "--with-sysroot=#{options.sysroot.realpath}", + '--enable-languages=c,c++,lto', + '--enable-lto', + '--enable-shared', + '--with-arch=rv32imafdc', + '--with-abi=ilp32d', + '--with-tune=rocket', + '--with-isa-spec=20191213', + '--disable-bootstrap', + '--disable-multilib', + '--enable-checking=release', + '--disable-libssp', + '--disable-libquadmath', + '--enable-threads=posix', + '--with-default-libstdcxx-abi=new', + '--disable-nls', + "--target=#{options.target}", + "--build=#{options.build}", + "--host=#{options.build}" + + ] + flags = '-O2 -fPIC' + env = { + 'CFLAGS' => flags, + 'CXXFLAGS' => flags, + 'PATH' => "#{options.rootfs.realpath + 'bin'}:#{ENV['PATH']}" + } + configure = source_directory.relative_path_from(cwd) + 'configure' + sh env, configure.to_path, *configure_options, chdir: cwd.to_path + sh env, 'make', '-j', Etc.nprocessors.to_s, chdir: cwd.to_path + sh env, 'make', 'install', chdir: cwd.to_path + end +end + +task cross: ['cross:binutils', 'cross:gcc1', 'cross:headers', 'cross:kernel', 'cross:glibc', 'cross:gcc2'] do +end diff --git a/src/Main.hs b/src/Main.hs index bb8be4c..4004d5c 100644 --- a/src/Main.hs +++ b/src/Main.hs @@ -1,13 +1,95 @@ -module Main where +module Main + ( main + ) where -import Language.Elna.Parser (programP) -import Text.Megaparsec (runParser, errorBundlePretty) -import qualified Data.Text.IO as Text +import Language.Elna.Object.Elf + ( ByteOrder(..) + , Elf32_Ehdr(..) + , Elf32_Off + , ElfMachine(..) + , ElfType(..) + , ElfVersion(..) + , ElfIdentification(..) + , ElfClass(..) + , ElfData(..) + , Elf32_Shdr(..) + , ElfSectionType(..) + , elf32Ehdr + , elf32Shdr + ) +import qualified Data.ByteString as ByteString +import qualified Data.ByteString.Builder as ByteString.Builder +import Data.Vector (Vector) +import qualified Data.Vector as Vector +import Control.Exception (throwIO) +import System.IO (Handle, IOMode(..), SeekMode(..), hSeek, withFile) +import Data.Foldable (traverse_) + +riscv32Elf :: FilePath -> IO () +riscv32Elf outFile = withFile outFile WriteMode withObjectFile + where + headerSize :: Elf32_Off + headerSize = 52 + withObjectFile objectHandle + = hSeek objectHandle AbsoluteSeek (fromIntegral headerSize) + >> putContents objectHandle + >>= afterContents objectHandle + afterContents objectHandle headers = + let headerEncodingResult = elf32Ehdr + $ header + $ Vector.foldr ((+) . sh_size) 0 headers + in traverse_ (ByteString.Builder.hPutBuilder objectHandle . elf32Shdr LSB) headers + >> either throwIO (putHeaders objectHandle) headerEncodingResult + putHeaders objectHandle encodedHeader + = hSeek objectHandle AbsoluteSeek 0 + >> ByteString.Builder.hPutBuilder objectHandle encodedHeader + putContents :: Handle -> IO (Vector Elf32_Shdr) + putContents objectHandle = + let stringTable = "\0shstrtab\0" + written = Vector.fromList + [ Elf32_Shdr + { sh_type = SHT_NULL + , sh_size = 0 + , sh_offset = 0 + , sh_name = 0 + , sh_link = 0 + , sh_info = 0 + , sh_flags = 0 + , sh_entsize = 0 + , sh_addralign = 0 + , sh_addr = 0 + } + , Elf32_Shdr + { sh_type = SHT_STRTAB + , sh_size = fromIntegral $ ByteString.length stringTable + , sh_offset = fromIntegral headerSize + , sh_name = 1 + , sh_link = 0 + , sh_info = 0 + , sh_flags = 0 + , sh_entsize = 0 + , sh_addralign = 0 + , sh_addr = 0 + } + ] + in ByteString.hPut objectHandle stringTable + >> pure written + header written = Elf32_Ehdr + { e_version = EV_CURRENT + , e_type = ET_REL + , e_shstrndx = 1 -- String table. SHN_UNDEF + , e_shoff = written + headerSize + , e_shnum = 2 + , e_shentsize = 40 + , e_phoff = 0 + , e_phnum = 0 + , e_phentsize = 32 + , e_machine = EM_RISCV + , e_ident = ElfIdentification ELFCLASS32 ELFDATA2LSB + , e_flags = 0x4 -- EF_RISCV_FLOAT_ABI_DOUBLE + , e_entry = 0 + , e_ehsize = fromIntegral headerSize + } main :: IO () -main = Text.getContents - >>= withParseResult . runParser programP "" - where - withParseResult (Right _) = pure () - withParseResult (Left errorBundle) = - putStr $ errorBundlePretty errorBundle +main = riscv32Elf "here.o"