[Solved] GNU LD section attributes for 64-bit kernel ELF

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
startrail
Posts: 12
Joined: Tue Jan 05, 2016 9:10 am

[Solved] GNU LD section attributes for 64-bit kernel ELF

Post by startrail »

I am trying to link a 64-bit kernel ELF using GNU LD. I have a executable section named `lowerhalf` and then the other usual sections. The linker script I use is this

Code: Select all

ENTRY(kernelCompatibilityModeStart);

SECTIONS {
	. = 0x80000000; /* lowerhalf origin */
	.lowerhalf : ALIGN(0x1000) {
		*(.lowerhalf);
	}

	. = 0xffffffff80000000; /* higherhalf origin */
	.GDT64 : ALIGN(0x1000) {
		*(.GDT64);
	}
	.IDT64 : ALIGN(0x1000) {
		*(.IDT64);
	}
	.TSS64 : ALIGN(0x1000) {
		*(.TSS64);
	}
	.KERNELSTACK : ALIGN(0x1000) {
		*(.KERNELSTACK);
	}
	.ISTs : ALIGN(0x1000) {
		*(.ISTs);
	}
	.text : ALIGN(0x1000) {
		*(.text);
	}
	.data : ALIGN(0x1000) {
		*(.data);
	}
	.rodata : ALIGN(0x1000) {
		*(.rodata*);
	}
	.bss : ALIGN(0x1000) {
		*(COMMON);
		*(.bss);
	}
}
When I look at the generated segments using `readelf` I see something like

Code: Select all

Elf file type is EXEC (Executable file)
Entry point 0x80000000
There are 3 program headers, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000001000 0x0000000080000000 0x0000000080000000
                 0x0000000000000030 0x0000000000000030  R      0x1000
  LOAD           0x0000000000002000 0xffffffff80000000 0xffffffff80000000
                 0x0000000000026718 0x0000000000026718  R E    0x1000
  LOAD           0x0000000000029000 0xffffffff80027000 0xffffffff80027000
                 0x0000000000004954 0x00000000000053d8  RW     0x1000

 Section to Segment mapping:
  Segment Sections...
   00     .lowerhalf 
   01     .GDT64 .IDT64 .TSS64 .KERNELSTACK .ISTs .text
   02     .data .ctors .rodata .eh_frame .bss
Some problems with the output ELF:
1. the `.text` section gets bundled up with other sections like `.GDT64` and `.KERNELSTACK`
2. `.lowerhalf` should have `RE` attributes instead of just `R`
3. all `.data`, `.ctors`, and `.rodata` end up with the `RW` flags.

I tried using the `MEMORY` directive but that results in a load of linker errors or warnings and doesn't give the desired output.

I'd like for the sections to have appropriate `RWE` attributes so I can create my paging entries accordingly.
How do I add the correct attributes?
Last edited by startrail on Tue Feb 21, 2023 5:37 pm, edited 1 time in total.
MichaelPetch
Member
Member
Posts: 797
Joined: Fri Aug 26, 2016 1:41 pm
Libera.chat IRC: mpetch

Re: GNU LD section attributes and flags for 64-bit kernel EL

Post by MichaelPetch »

Was there a Windows tools chain involved in this at some point?

When you defined .GDT64 .IDT64 .TSS64 .KERNELSTACK .ISTs sections did you specify what kind of attributes you wanted on the sections in the assembly code? Sounds like you didn't specify any and it defaulted to attributes associated with the .text section.
startrail
Posts: 12
Joined: Tue Jan 05, 2016 9:10 am

Re: GNU LD section attributes and flags for 64-bit kernel EL

Post by startrail »

No, there's no Windows or MinGW toolchain involved. Cross-compiling on Ubuntu 22.04 with GCC 12.1.
I didn't know you could specify section attributes in the ASM file itself. I'm using NASM for my assembly files.
Is there any doc or example you could point me to?

Edit: Looks like NASM 8.9.2 ELF extensions to the SECTION Directive (https://www.nasm.us/doc/nasmdoc8.html#section-8.9.2) may be relevant here. I'll look into it.
nexos
Member
Member
Posts: 1081
Joined: Tue Feb 18, 2020 3:29 pm
Libera.chat IRC: nexos

Re: GNU LD section attributes and flags for 64-bit kernel EL

Post by nexos »

When you declare .lowerhalf in your NASM file, do

Code: Select all

section .lowerhalf progbits alloc exec nowrite align=4096
And do the same for other sections, just change the attributes as needed.

You said that several sections got "bundled". Technically, they are in different sections (you would see that if read the section headers with readelf), but they all got placed in one segment as they have the same attributes and are contiguous. TBH, I wouldn't try to change that, unless you have a specific reason why you want them in separate segments.
"How did you do this?"
"It's very simple — you read the protocol and write the code." - Bill Joy
Projects: NexNix | libnex | nnpkg
nullplan
Member
Member
Posts: 1789
Joined: Wed Aug 30, 2017 8:24 am

Re: GNU LD section attributes and flags for 64-bit kernel EL

Post by nullplan »

startrail wrote: 3. all `.data`, `.ctors`, and `.rodata` end up with the `RW` flags.
Well, duh. You are telling the linker to place .rodata after .data, and .ctors is not mentioned at all, meaning the linker just has to link it anywhere. My guess is you never thought about what sections needed to be writable. That would also explain the excessive 4k alignment all over the place.

For reference, here's my linker script again:

Code: Select all

ENTRY(_kstart)
OUTPUT_FORMAT("elf64-x86-64")

PHDRS {
    headers PT_PHDR PHDRS;
    text PT_LOAD FILEHDR PHDRS;
    data PT_LOAD;
}

SECTIONS {
    . = 0xffffffff80000000 + SIZEOF_HEADERS;
    .text : {
        *(.text)
        *(.text.*)
    } :text
    .rodata : {
        *(.rodata)
        *(.rodata.*)
    }

    .eh_frame_hdr : {
        __eh_frame_hdr = .;
        *(.eh_frame_hdr)
    }
    .eh_frame : {
        *(.eh_frame)
        *(.eh_frame.*)
    }

    /* Normally, the overlap between text and data section is handled by having
     * two different pages for the last bits of text and the first bits of data.
     * That way, if the last bits of text are overwritten, it won't affect the
     * text that is actually used. Unfortunately, for the kernel this is not
     * possible. The whole file is loaded into memory en bloc, so the same page
     * would be mapped twice. Therefore, a write access to the writable page
     * would end up being visible in the non-writable side of things. Therefore,
     * we must actually page-align here.
     */
    . = ALIGN(2M);
    .data : {
        *(.data)
        *(.data.*)
    } :data
    .bss : {
        *(.bss)
        *(COMMON)
        *(.bss.*)
    }
}
Note that I don't have a lower half. I get the paging set up by the bootloader. Where the bootloader does not provide for that, I add a shim that does it for me. This allows me to completely uncouple the unpaged and paged code.

Alignment directives should never be necessary in the linker script because you already have section alignment in all the input sections. And even there, page alignment is rarely necessary. I only use one single alignment directive to cause a page break between the writable and non.writable sections. And I could normally do that well enough with just ". += 2M" were it not for the reasoning outlined in the linker script.
Carpe diem!
startrail
Posts: 12
Joined: Tue Jan 05, 2016 9:10 am

Re: GNU LD section attributes and flags for 64-bit kernel EL

Post by startrail »

nullplan's linker script definitely helped a lot
My guess is you never thought about what sections needed to be writable.
Well yes, that linker script was written 8 years ago when I thought it was cool to have as many segments as possible, see their names pop up in readelf output, and wanted to get to long mode as quickly as possible. I'm moving them to appropriate .data and .bss sections and adding NX bit to my paging entries. Hence the question.
When you declare .lowerhalf in your NASM file, do

Code: Select all

section .lowerhalf progbits alloc exec nowrite align=4096
Did that and got RE attributes as expected. Thanks MichaelPetch and nexos.

I use the lowerhalf so I can load a 64-bit GDT to get out of the compatibility mode setup by 32-bit stage2 loader. Now that I think about it, my stage2 loader already does all the paging and 'dummy' 64-bit GDT setup before jumping to kernelCompatibilityModeStart, which looks like may actually be in true long mode. I may not even need the lowerhalf stuff.

The linker script that works for now is

Code: Select all

ENTRY(kernelCompatibilityModeStart)

PHDRS {
	lowerhalf PT_LOAD;
	text PT_LOAD;
	rodata PT_LOAD;
	data PT_LOAD;
}

SECTIONS {
	. = 0x80000000;
	.lowerhalf : {
		*(.lowerhalf)
	} : lowerhalf

	. = 0xffffffff80000000;
	.text : {
		*(.text)
		*(.text.*)
	} : text

	. = ALIGN(0x1000);
	.rodata : {
		*(.rodata)
		*(.rodata.*)
	} : rodata

	.eh_frame_hdr : {
		*(.eh_frame_hdr)
	}
	.eh_frame : {
		*(.eh_frame)
		*(.eh_frame.*)
	}

	. = ALIGN(0x1000);
	.data : {
		*(.data)
		*(.data.*)
	} : data
	.bss : {
		*(.bss)
		*(.bss.*)
		*(COMMON)
	}
}
Post Reply