diff --git a/lib/caotral/binary/elf/section.rb b/lib/caotral/binary/elf/section.rb index 01bb6fe..1590358 100644 --- a/lib/caotral/binary/elf/section.rb +++ b/lib/caotral/binary/elf/section.rb @@ -22,13 +22,18 @@ def build end def layout!(offset:) - size = build.bytesize + size = file_size align = [@header.addralign.to_i, 1].max offset = (offset + align - 1) / align * align - @header.set!(offset:, size:) - - offset + size + if header.nobits? + header.set!(offset:) + else + header.set!(offset:, size:) + end + offset + file_size end + def file_size = header.nobits? ? 0 : build.bytesize + def memory_size = header.nobits? ? header.size : build.bytesize end end end diff --git a/lib/caotral/binary/elf/section_header.rb b/lib/caotral/binary/elf/section_header.rb index 655f0ca..0569d3f 100644 --- a/lib/caotral/binary/elf/section_header.rb +++ b/lib/caotral/binary/elf/section_header.rb @@ -56,6 +56,7 @@ def addralign = @addralign.pack("C*").unpack1("Q<") def allocated? = (@flags.pack("C*").unpack1("Q<") & SHF[:ALLOC]) != 0 def writable? = (@flags.pack("C*").unpack1("Q<") & SHF[:WRITE]) != 0 def execinstr? = (@flags.pack("C*").unpack1("Q<") & SHF[:EXECINSTR]) != 0 + def nobits? = type == :nobits private def bytes = [@name, @type, @flags, @addr, @offset, @size, @link, @info, @addralign, @entsize] end diff --git a/lib/caotral/linker/builder.rb b/lib/caotral/linker/builder.rb index e5353df..8354ba2 100644 --- a/lib/caotral/linker/builder.rb +++ b/lib/caotral/linker/builder.rb @@ -88,6 +88,12 @@ def build section_name: ".strtab", header: Caotral::Binary::ELF::SectionHeader.new ) + bss_section = Caotral::Binary::ELF::Section.new( + body: nil, + section_name: ".bss", + header: Caotral::Binary::ELF::SectionHeader.new + .set!(type: SHT[:nobits], flags: SHF[:ALLOC] | SHF[:WRITE], addralign: 8) + ) symtab_section = Caotral::Binary::ELF::Section.new( body: [], section_name: ".symtab", @@ -119,14 +125,19 @@ def build rel_sections = [] rel_texts = [] pending_relative_relocations = [] + dynamic_symbol_pairs = [] elf.header = elf_obj.header.dup strtab_names = [] text_offsets = {} rodata_offsets = {} + bss_offsets = {} data_offsets = {} got_plt_offsets = {} text_offset = 0 rodata_offset = 0 + bss_offset = 0 + bss_present = false + bss_addralign = 1 data_offset = 0 got_plt_offset = 24 sym_by_elf = Hash.new { |h, k| h[k] = [] } @@ -154,6 +165,15 @@ def build data_offsets[elf_obj.object_id] = data_offset data_offset += data.body.bytesize end + bss = elf_obj.find_by_name(".bss") + unless bss.nil? + bss_present = true + alignment = [bss.header.addralign, 1].max + bss_offset = align_up(bss_offset, alignment) + bss_addralign = [bss_addralign, alignment].compact.max + bss_offsets[elf_obj.object_id] = bss_offset + bss_offset += bss.memory_size + end strtab = elf_obj.find_by_name(".strtab") strtab.body.names.split("\0").each { |name| strtab_names << name } unless strtab.nil? symtab = elf_obj.find_by_name(".symtab") @@ -161,6 +181,7 @@ def build unless symtab.nil? base_index = symtab_section.body.size text_index = elf_obj.sections.index(text) unless text.nil? + bss_index = elf_obj.sections.index(bss) unless bss.nil? data_index = elf_obj.sections.index(data) unless data.nil? rodata_index = elf_obj.sections.index(rodata) unless rodata.nil? @@ -169,6 +190,7 @@ def build name, info, other, shndx, value, size = st.build.unpack("L void + + def file_size: () -> Integer + def memory_size: () -> Integer end diff --git a/sig/caotral/binary/elf/section_header.rbs b/sig/caotral/binary/elf/section_header.rbs index 3553098..3f18fa3 100644 --- a/sig/caotral/binary/elf/section_header.rbs +++ b/sig/caotral/binary/elf/section_header.rbs @@ -26,4 +26,5 @@ class Caotral::Binary::ELF::SectionHeader def addr: () -> Integer def writable?: () -> bool def execinstr?: () -> bool + def nobits?: () -> bool end diff --git a/test/caotral/linker/integration/bss_test.rb b/test/caotral/linker/integration/bss_test.rb new file mode 100644 index 0000000..e47003d --- /dev/null +++ b/test/caotral/linker/integration/bss_test.rb @@ -0,0 +1,131 @@ +require_relative "../../../test_suite" + +class Caotral::Linker::BSSTest < Test::Unit::TestCase + include TestProcessHelper + attr_reader :output, :inputs + def teardown + inputs.each { |input| File.delete(input) unless input.nil? } + File.delete(output) if File.exist?(output) + end + def test_bss + path = Pathname.new("sample/assembler/bss.s").to_s + input = "bss.o" + IO.popen(["as", "-o", input, path]).close + @inputs = [input] + @output = "bss" + + Caotral::Linker.link!(inputs:, output:, linker: "self") + + reader = Caotral::Binary::ELF::Reader.new(input: output, debug: false) + elf = reader.read + bss = elf.find_by_name(".bss") + + assert_equal(:nobits, bss.header.type) + assert_equal(8, bss.header.size) + + IO.popen(["./#{@output}"]).close + exit_code, handle_code = check_process($?.to_i) + assert_equal(42, exit_code) + assert_equal(0, handle_code) + end + + def test_initialize_zero_bss + path = Pathname.new("sample/assembler/bss_initialize_zero.s").to_s + input = "bss.o" + IO.popen(["as", "-o", input, path]).close + @inputs = [input] + @output = "bss" + + Caotral::Linker.link!(inputs:, output:, linker: "self") + + reader = Caotral::Binary::ELF::Reader.new(input: output, debug: false) + elf = reader.read + bss = elf.find_by_name(".bss") + + assert_equal(:nobits, bss.header.type) + assert_equal(32, bss.header.size) + + IO.popen(["./#{@output}"]).close + exit_code, handle_code = check_process($?.to_i) + assert_equal(42, exit_code) + assert_equal(0, handle_code) + end + + def test_initialize_zero_pie_bss + path = Pathname.new("sample/assembler/bss_initialize_zero.s").to_s + input = "bss.o" + IO.popen(["as", "-o", input, path]).close + @inputs = [input] + @output = "bss" + + Caotral::Linker.link!(inputs:, output:, linker: "self", pie: true) + + reader = Caotral::Binary::ELF::Reader.new(input: output, debug: false) + elf = reader.read + bss = elf.find_by_name(".bss") + dynamic_symbol = elf.find_by_name(".dynsym").body.find { |sym| sym.name_string == "value" } + + assert_equal(elf.index(".bss"), dynamic_symbol.shndx) + assert_equal(:nobits, bss.header.type) + assert_equal(32, bss.header.size) + assert_equal(bss.header.addr + 24, dynamic_symbol.value) + + IO.popen(["./#{@output}"]).close + exit_code, handle_code = check_process($?.to_i) + assert_equal(42, exit_code) + assert_equal(0, handle_code) + end + + def test_multiple_bss + @inputs = [] + [ + [Pathname.new("sample/assembler/bss.s").to_s, "bss.o"], + [Pathname.new("sample/assembler/two_bss_sections.s").to_s, "two_bss_section.o"], + ].each do |(path, input)| + IO.popen(["as", "-o", input, path]).close + @inputs << input + end + @output = "bss" + + Caotral::Linker.link!(inputs:, output:, linker: "self", pie: true) + + reader = Caotral::Binary::ELF::Reader.new(input: output, debug: false) + elf = reader.read + bss = elf.find_by_name(".bss") + aligned_symbol = elf.find_by_name(".symtab").body.find { |sym| sym.name_string == "aligned_value" } + + assert_equal(elf.index(".bss"), aligned_symbol.shndx) + assert_equal(32, bss.header.size) + assert_equal(16, bss.header.addralign) + assert_equal(16, aligned_symbol.value) + + IO.popen(["./#{@output}"]).close + exit_code, handle_code = check_process($?.to_i) + assert_equal(42, exit_code) + assert_equal(0, handle_code) + end + + def test_zero_size_bss + @inputs = [] + [ + [Pathname.new("sample/assembler/empty_bss.s").to_s, "bss.o"], + ].each do |(path, input)| + IO.popen(["as", "-o", input, path]).close + @inputs << input + end + @output = "bss" + + Caotral::Linker.link!(inputs:, output:, linker: "self") + + reader = Caotral::Binary::ELF::Reader.new(input: output, debug: false) + elf = reader.read + bss = elf.find_by_name(".bss") + symbol = elf.find_by_name(".symtab").body.find { |sym| sym.name_string == "value" } + + assert_not_nil(bss) + assert_equal(elf.index(".bss"), symbol.shndx) + assert_equal(0, bss.header.size) + assert_equal(8, bss.header.addralign) + assert_equal(0, symbol.value) + end +end