From 86d79973fb93b10e48ca63d19dc651645b6fd7dc Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Thu, 13 Aug 2026 23:58:07 +0900 Subject: [PATCH 1/4] add initial bss section handling --- lib/caotral/binary/elf/section.rb | 13 ++++++--- lib/caotral/binary/elf/section_header.rb | 1 + lib/caotral/linker/builder.rb | 22 +++++++++++++-- sample/assembler/bss.s | 16 +++++++++++ test/caotral/linker/integration/bss_test.rb | 31 +++++++++++++++++++++ 5 files changed, 76 insertions(+), 7 deletions(-) create mode 100644 sample/assembler/bss.s create mode 100644 test/caotral/linker/integration/bss_test.rb diff --git a/lib/caotral/binary/elf/section.rb b/lib/caotral/binary/elf/section.rb index 01bb6fe3..1590358c 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 655f0caa..0569d3fe 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 e5353df2..ebf669f2 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", @@ -123,10 +129,12 @@ def build strtab_names = [] text_offsets = {} rodata_offsets = {} + bss_offsets = {} data_offsets = {} got_plt_offsets = {} text_offset = 0 rodata_offset = 0 + bss_offset = 0 data_offset = 0 got_plt_offset = 24 sym_by_elf = Hash.new { |h, k| h[k] = [] } @@ -154,6 +162,11 @@ 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_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 +174,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 +183,7 @@ def build name, info, other, shndx, value, size = st.build.unpack("L 0 sections << symtab_section + sections << strtab_section rel_sections.each { |s| sections << s.dup } @@ -357,6 +371,8 @@ def build sections << shstrtab_section + bss_section.header.set!(size: bss_offset) + shstrtab_section_names = [*sections.map(&:section_name), "\0"].join("\0") shstrtab_section.body.names = shstrtab_section_names diff --git a/sample/assembler/bss.s b/sample/assembler/bss.s new file mode 100644 index 00000000..e6339852 --- /dev/null +++ b/sample/assembler/bss.s @@ -0,0 +1,16 @@ +.section .bss +.balign 8 +.globl value +.type value, @object +.size value, 8 +value: + .zero 8 + +.text +.globl _start +.type _start, @function +_start: + movq $42, value(%rip) + movq value(%rip), %rdi + movq $60, %rax + syscall diff --git a/test/caotral/linker/integration/bss_test.rb b/test/caotral/linker/integration/bss_test.rb new file mode 100644 index 00000000..4841eda2 --- /dev/null +++ b/test/caotral/linker/integration/bss_test.rb @@ -0,0 +1,31 @@ +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 +end From 1cc5fd49fb3f03d78594d43fc46ac2e4d9d5da75 Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Thu, 13 Aug 2026 23:59:32 +0900 Subject: [PATCH 2/4] add bss section type signatures --- sig/caotral/binary/elf/section.rbs | 3 +++ sig/caotral/binary/elf/section_header.rbs | 1 + 2 files changed, 4 insertions(+) diff --git a/sig/caotral/binary/elf/section.rbs b/sig/caotral/binary/elf/section.rbs index 9933f46c..ed4d53dc 100644 --- a/sig/caotral/binary/elf/section.rbs +++ b/sig/caotral/binary/elf/section.rbs @@ -8,4 +8,7 @@ class Caotral::Binary::ELF::Section body: untyped, section_name: String? ) -> 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 35530981..3f18fa3e 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 From 7c02b520e111b07568938cdb7216e5968dba01e8 Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Sat, 15 Aug 2026 00:31:43 +0900 Subject: [PATCH 3/4] fix bss section layout --- lib/caotral/linker/builder.rb | 11 ++- lib/caotral/linker/layout.rb | 16 +++-- sample/assembler/bss_initialize_zero.s | 27 ++++++++ sample/assembler/two_bss_sections.s | 7 ++ test/caotral/linker/integration/bss_test.rb | 76 +++++++++++++++++++++ 5 files changed, 132 insertions(+), 5 deletions(-) create mode 100644 sample/assembler/bss_initialize_zero.s create mode 100644 sample/assembler/two_bss_sections.s diff --git a/lib/caotral/linker/builder.rb b/lib/caotral/linker/builder.rb index ebf669f2..dfd03ed7 100644 --- a/lib/caotral/linker/builder.rb +++ b/lib/caotral/linker/builder.rb @@ -125,6 +125,7 @@ def build rel_sections = [] rel_texts = [] pending_relative_relocations = [] + dynamic_symbol_pairs = [] elf.header = elf_obj.header.dup strtab_names = [] text_offsets = {} @@ -135,6 +136,7 @@ def build text_offset = 0 rodata_offset = 0 bss_offset = 0 + bss_addralign = 1 data_offset = 0 got_plt_offset = 24 sym_by_elf = Hash.new { |h, k| h[k] = [] } @@ -164,6 +166,9 @@ def build end bss = elf_obj.find_by_name(".bss") unless bss.nil? + 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 @@ -338,6 +343,7 @@ def build name = dynstr.body.offset_of(copy_sym.name_string) end copy_sym.name_string = sym.name_string + dynamic_symbol_pairs << [copy_sym, sym] dynsym.body << copy_sym.set!(name:, shndx:, value: sym.value) end hash = Caotral::Binary::ELF::Section::Hash.new(nchain: dynsym.body.size) @@ -371,7 +377,7 @@ def build sections << shstrtab_section - bss_section.header.set!(size: bss_offset) + bss_section.header.set!(size: bss_offset, addralign: bss_addralign) shstrtab_section_names = [*sections.map(&:section_name), "\0"].join("\0") shstrtab_section.body.names = shstrtab_section_names @@ -392,6 +398,8 @@ def build end end + dynamic_symbol_pairs.each { |copy_sym, sym| copy_sym.set!(shndx: sym.shndx) } + defined_global_index = {} symtab_section.body.each_with_index do |sym, index| next unless sym.bind == SYMTAB_BIND[:globals] @@ -551,6 +559,7 @@ def ref_index(sections, section_name) def rel_type(section) = section.section_name&.start_with?(".rela.") ? 4 : 9 def rel_entsize(section) = section.section_name&.start_with?(".rela.") ? 24 : 16 + def align_up(val, align) = (val + align - 1) / align * align def dynamic? = @shared || @pie end diff --git a/lib/caotral/linker/layout.rb b/lib/caotral/linker/layout.rb index 70014039..546b22df 100644 --- a/lib/caotral/linker/layout.rb +++ b/lib/caotral/linker/layout.rb @@ -118,15 +118,23 @@ def finalize_program_headers! when :LOAD allocated_sections = @load_sections[ph.flags] - section_end = allocated_sections.map { |s| s.header.offset + s.header.size }.max + memory_end = allocated_sections.map { |s| s.header.offset + s.memory_size }.max + file_end = allocated_sections + .reject { |s| s.header.nobits? } + .map { |s| s.header.offset + s.file_size }.max segment_start = ph.flags == :R ? 0 : allocated_sections.map { |s| s.header.offset }.min - segment_end = ph.flags == :R ? [header_end, section_end].compact.max : section_end + if ph.flags == :R + memory_end = [header_end, memory_end].compact.max + file_end = [header_end, file_end].compact.max + end + memory_end ||= segment_start + file_end ||= segment_start offset = segment_start vaddr = @load_bias + offset paddr = vaddr - filesz = segment_end - segment_start - memsz = filesz + filesz = file_end - segment_start + memsz = memory_end - segment_start flags = PF[ph.flags] align = LOAD_ALIGNMENT when :INTERP diff --git a/sample/assembler/bss_initialize_zero.s b/sample/assembler/bss_initialize_zero.s new file mode 100644 index 00000000..ee0cb563 --- /dev/null +++ b/sample/assembler/bss_initialize_zero.s @@ -0,0 +1,27 @@ +.section .bss +.balign 8 +.zero 24 + +.globl value +.type value, @object +.size value, 8 +value: + .zero 8 + +.text +.globl _start +.type _start, @function +_start: + cmpq $0, value(%rip) + jne .Lnot_zero + + movq $42, value(%rip) + movq value(%rip), %rdi + jmp .Lexit + +.Lnot_zero: + movq $1, %rdi + +.Lexit: + movq $60, %rax + syscall diff --git a/sample/assembler/two_bss_sections.s b/sample/assembler/two_bss_sections.s new file mode 100644 index 00000000..4a1fd391 --- /dev/null +++ b/sample/assembler/two_bss_sections.s @@ -0,0 +1,7 @@ +.section .bss +.balign 16 +.globl aligned_value +.type aligned_value, @object +.size aligned_value, 16 +aligned_value: + .zero 16 diff --git a/test/caotral/linker/integration/bss_test.rb b/test/caotral/linker/integration/bss_test.rb index 4841eda2..a7d436fd 100644 --- a/test/caotral/linker/integration/bss_test.rb +++ b/test/caotral/linker/integration/bss_test.rb @@ -28,4 +28,80 @@ def test_bss 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 end From 0608fb8659a43a897d35925533766f4d86d11489 Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Sat, 15 Aug 2026 00:50:59 +0900 Subject: [PATCH 4/4] emit zero-size bss sections --- lib/caotral/linker/builder.rb | 4 +++- sample/assembler/empty_bss.s | 15 +++++++++++++ test/caotral/linker/integration/bss_test.rb | 24 +++++++++++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 sample/assembler/empty_bss.s diff --git a/lib/caotral/linker/builder.rb b/lib/caotral/linker/builder.rb index dfd03ed7..8354ba2a 100644 --- a/lib/caotral/linker/builder.rb +++ b/lib/caotral/linker/builder.rb @@ -136,6 +136,7 @@ def build text_offset = 0 rodata_offset = 0 bss_offset = 0 + bss_present = false bss_addralign = 1 data_offset = 0 got_plt_offset = 24 @@ -166,6 +167,7 @@ def build 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 @@ -362,7 +364,7 @@ def build sections << dynamic_section end - sections << bss_section if bss_offset > 0 + sections << bss_section if bss_present sections << symtab_section sections << strtab_section diff --git a/sample/assembler/empty_bss.s b/sample/assembler/empty_bss.s new file mode 100644 index 00000000..0ffc9987 --- /dev/null +++ b/sample/assembler/empty_bss.s @@ -0,0 +1,15 @@ +.section .bss +.balign 8 +.globl value +.type value, @object +.size value, 0 +value: + +.text +.globl _start +.type _start, @function +_start: + movq $42, value(%rip) + movq value(%rip), %rdi + movq $60, %rax + syscall diff --git a/test/caotral/linker/integration/bss_test.rb b/test/caotral/linker/integration/bss_test.rb index a7d436fd..e47003d7 100644 --- a/test/caotral/linker/integration/bss_test.rb +++ b/test/caotral/linker/integration/bss_test.rb @@ -104,4 +104,28 @@ def test_multiple_bss 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