Page 1 of 2

GCC emits mov instead of lea

Posted: Sun Sep 15, 2024 8:14 am
by SomePerson
For my IDT I load function pointers into the IDT, but instead of loading the address of the function pointers gcc emits a mov and loads 8 bytes from the function instead. Additionally this works correctly if the function is in the same file.

Example:

Code: Select all

IDT_set_gate(0, ISR0, ...)
Emits:

Code: Select all

mov -0x...(%rip), %rsi
Instead of:

Code: Select all

lea -0x...(%rip), %rsi
But

Code: Select all

IDT_set_gate(0, IDT_initialize, ...)
With IDT_initialize in the same file emits the correct lea.

What is causing this and how can I fix this?

Re: GCC emits mov instead of lea

Posted: Sun Sep 15, 2024 8:34 am
by nullplan
There's not enough code here to tell what is going on. But I suspect you declare ISR0 with the wrong type in C. Typically, it should be something like

Code: Select all

extern const char ISR0[];
And I suspect you have something like

Code: Select all

extern unsigned long ISR0;
How close am I?

Re: GCC emits mov instead of lea

Posted: Sun Sep 15, 2024 8:40 am
by SomePerson
ISR0 is a function declared like this:

Code: Select all

void ISR0();

Re: GCC emits mov instead of lea

Posted: Sun Sep 15, 2024 9:33 am
by iansjack
Do you declare ISR0() in the file containing the IDT functions? If not the compiler will assume that ISR0 an int rather than a pointer to a function.

Re: GCC emits mov instead of lea

Posted: Sun Sep 15, 2024 9:37 am
by SomePerson
Yes and the same problem also occurs with functions that I can call that are in another file.

Re: GCC emits mov instead of lea

Posted: Sun Sep 15, 2024 9:38 am
by iansjack
I think, as null plan says, that we need to see the code.

Re: GCC emits mov instead of lea

Posted: Sun Sep 15, 2024 9:46 am
by SomePerson
isr.c:

Code: Select all

void ISR0();
void ISR1();
void ISR2();
void ISR3();
void ISR4();
...

void ISR_initialize()
{
    IDT_set_gate(0, ISR_initialize, GDT_KCS, 0, 0xf); // "Works"
    //	-> lea -0x1e(%rip), %rsi
    IDT_set_gate(0, ISR0, GDT_KCS, 0, 0xf); // Does not work
    //	-> mov -0x33a(%rip), %rsi
    IDT_set_gate(1, ISR1, GDT_KCS, 0, 0xf);
    IDT_set_gate(2, ISR2, GDT_KCS, 0, 0xf);
    IDT_set_gate(3, ISR3, GDT_KCS, 0, 0xf);
    IDT_set_gate(4, ISR4, GDT_KCS, 0, 0xf);
    ...
}
idt.c:

Code: Select all

void IDT_set_gate(int interrupt, void (*base)(), uint16_t segment, int privilege, int type)
{
    if(interrupt > 255)return;

    idt[interrupt].base_low = ((uintptr_t)base) & 0xffff;
    idt[interrupt].base_mid = (((uintptr_t)base) >> 16) & 0xffff;
    idt[interrupt].base_high = ((uintptr_t)base) >> 32;
    idt[interrupt].segment = segment;
    idt[interrupt].flags = (privilege << 13) | ((type & 0xf) << 8);
    idt[interrupt].flags |= IDT_ENABLE_FLAG;
}

Re: GCC emits mov instead of lea

Posted: Sun Sep 15, 2024 1:16 pm
by sebihepp
Try &ISR0 instead of ISR0

Re: GCC emits mov instead of lea

Posted: Sun Sep 15, 2024 1:33 pm
by SomePerson
1: I tried &ISR0, but it did not work. (Also that should be equivalent to ISR0 anyway)

2: I got around the problem by doing this:

Code: Select all

void (*isrs[])() = {
    ISR0, ISR1, ISR2, ISR3,
    ISR4, ISR5, ISR6, ISR7,
    ISR8, ISR9, ISR10, ISR11,
    ISR12, ISR13, ISR14, ISR15,
    ISR16, ISR17, ISR18, ISR19,
    ISR20, ISR21, ISR22, ISR23,
    ISR24, ISR25, ISR26, ISR27,
    ISR28, ISR29, ISR30, ISR31,
    ISR32, ISR33, ISR34, ISR35,
    ISR36, ISR37, ISR38, ISR39,
    ISR40, ISR41, ISR42, ISR43,
    ISR44, ISR45, ISR46, ISR47,
};
and then calling IDT_set_gate like this:

Code: Select all

IDT_set_gate(0, isrs[0], GDT_KCS, 0, 0xf);

Re: GCC emits mov instead of lea

Posted: Sun Sep 15, 2024 2:39 pm
by Octocontrabass
How are you compiling and linking this code? That looks a bit like a PIC relocation that didn't get linked correctly.

Re: GCC emits mov instead of lea

Posted: Sun Sep 15, 2024 3:33 pm
by SomePerson
Compiling:
gcc -Wall -Wextra -O3 -ffreestanding -nostdlib -c $< -o $@
Linking:
gcc -Tx86_64.ld -nostdlib $^ -lgcc -o $@
x86_64.ld:

Code: Select all

KERNEL_PM = 0x100000
KERNEL_VM = 0xffff800000100000
KERNEL_VM_OFFSET = KERNEL_VM - KERNEL_PM

ENTRY(k32_entry)
OUTPUT_FORMAT("binary")
SECTIONS
{
	. = KERNEL_PM;
	... k32 stuff
	. = ALIGN(4096);
	. += KERNEL_VM_OFFSET;
	.text : AT(ADDR(.text) - KERNEL_VM_OFFSET) {...}
	+ other sections
	+ discards
}
Also I am having the same problem again with an array (loads first 8 bytes instead of pointer).

Re: GCC emits mov instead of lea

Posted: Sun Sep 15, 2024 5:05 pm
by Octocontrabass
SomePerson wrote: Sun Sep 15, 2024 3:33 pmgcc -Wall -Wextra -O3 -ffreestanding -nostdlib -c $< -o $@
You should use a cross-compiler. Since you're not using a cross-compiler, you get whatever default options the package maintainers decided to use when they built your copy of GCC, and I'd guess they decided to enable PIC by default. I'm not sure why your resulting binary isn't statically linked correctly, but disabling PIC (-fno-pic) will fix that. If you disable PIC you'll also need to specify the code model according to the address where your kernel is linked. The current address you're using requires the large code model (-mcmodel=large), but if you change it to 0xFFFFFFFF80000000 or higher, you can use the kernel code model (-mcmodel=kernel).

You also need to disable the red zone (-mno-red-zone), and you probably want to disable the use of extended registers (-mgeneral-regs-only).

Re: GCC emits mov instead of lea

Posted: Mon Sep 16, 2024 5:30 am
by SomePerson
Thank you.

Re: GCC emits mov instead of lea

Posted: Thu Oct 10, 2024 8:41 am
by korangar
Hi

I'm working with a very simple example and discovered the same problem. GCC emits LEA instructions to pass local C functions as parameter and MOV instructions to pass extern functions:

Code: Select all

lea    0x0(%rip),%rax

vs

mov    0x0(%rip),%rax
Then, the linker modify those lines into these ones

Code: Select all

lea    -0x16(%rip),%rax

vs

mov    $0x40103b,%rax
Which makes sense after objdump-ing the .elf

However, if I set --oformat=binary the linker produces this code

Code: Select all

lea    -0x16(%rip),%rax

vs

mov    0xb(%rip),%rax
This MOV stores the content of 0xb(%rip) into RAX while LEA stores -0x16(%rip) into RAX which makes no sense at all.

The question is why the linker doesn't replace 0x0(%rip) with a constant (just like with the .elf), o replaces the MOV with LEA.

If I add -pie to the linker, the .elf version replaces the MOV with LEA and fixes the 0x0(%rip) with 0xb(%rip) but the flat binary doesn't.

Below you can find a minimal example, instructions to reproduce as well as the results I got.

c_code.c

Code: Select all

#include <stdint.h>

void take_func(uint64_t offset) {
}

int c_func() {
  return 0x101010;
}

extern int extern_func();

void _start() {
  take_func((uint64_t)&c_func);
  take_func((uint64_t)&extern_func);
}
extern_code.c

Code: Select all

int extern_func() {
  return 0x111111;
}
compile

Code: Select all

x86_64-linux-gnu-gcc -c c_code.c -o c_code.o
x86_64-linux-gnu-gcc -c extern_code.c -o extern_code.o
x86_64-linux-gnu-ld --oformat=binary *.o -o linked.bin
x86_64-linux-gnu-ld --oformat=elf64-x86-64 *.o -o linked.elf
I'm working with this Docker container: agodio/itba-so-multi-platform:3.0

objdump -D c_code.o

Code: Select all

000000000000000b <c_func>:
   b:	55                   	push   %rbp
   c:	48 89 e5             	mov    %rsp,%rbp
   f:	b8 10 10 10 00       	mov    $0x101010,%eax
  14:	5d                   	pop    %rbp
  15:	c3                   	ret

0000000000000016 <_start>:
  16:	55                   	push   %rbp
  17:	48 89 e5             	mov    %rsp,%rbp
  1a:	48 8d 05 00 00 00 00 	lea    0x0(%rip),%rax        # 21 <_start+0xb>
  21:	48 89 c7             	mov    %rax,%rdi
  24:	e8 00 00 00 00       	call   29 <_start+0x13>
  29:	48 8b 05 00 00 00 00 	mov    0x0(%rip),%rax        # 30 <_start+0x1a>
  30:	48 89 c7             	mov    %rax,%rdi
  33:	e8 00 00 00 00       	call   38 <_start+0x22>
  38:	90                   	nop
  39:	5d                   	pop    %rbp
  3a:	c3                   	ret
objdump -b binary -D -m i386:x86-64 linked.bin

Code: Select all

       b:       55                      push   %rbp
       c:       48 89 e5                mov    %rsp,%rbp
       f:       b8 10 10 10 00          mov    $0x101010,%eax
      14:       5d                      pop    %rbp
      15:       c3                      ret
      16:       55                      push   %rbp
      17:       48 89 e5                mov    %rsp,%rbp
      1a:       48 8d 05 ea ff ff ff    lea    -0x16(%rip),%rax        # 0xb
      21:       48 89 c7                mov    %rax,%rdi
      24:       e8 d7 ff ff ff          call   0x0
      29:       48 8b 05 0b 00 00 00    mov    0xb(%rip),%rax        # 0x3b
      30:       48 89 c7                mov    %rax,%rdi
      33:       e8 c8 ff ff ff          call   0x0
      38:       90                      nop
      39:       5d                      pop    %rbp
      3a:       c3                      ret
      3b:       55                      push   %rbp
      3c:       48 89 e5                mov    %rsp,%rbp
      3f:       b8 11 11 11 00          mov    $0x111111,%eax
      44:       5d                      pop    %rbp
      45:       c3                      ret
objdump -D linked.elf

Code: Select all

000000000040100b <c_func>:
  40100b:       55                      push   %rbp
  40100c:       48 89 e5                mov    %rsp,%rbp
  40100f:       b8 10 10 10 00          mov    $0x101010,%eax
  401014:       5d                      pop    %rbp
  401015:       c3                      ret

0000000000401016 <_start>:
  401016:       55                      push   %rbp
  401017:       48 89 e5                mov    %rsp,%rbp
  40101a:       48 8d 05 ea ff ff ff    lea    -0x16(%rip),%rax        # 40100b <c_func>
  401021:       48 89 c7                mov    %rax,%rdi
  401024:       e8 d7 ff ff ff          call   401000 <take_func>
  401029:       48 c7 c0 3b 10 40 00    mov    $0x40103b,%rax
  401030:       48 89 c7                mov    %rax,%rdi
  401033:       e8 c8 ff ff ff          call   401000 <take_func>
  401038:       90                      nop
  401039:       5d                      pop    %rbp
  40103a:       c3                      ret

000000000040103b <extern_func>:
  40103b:       55                      push   %rbp
  40103c:       48 89 e5                mov    %rsp,%rbp
  40103f:       b8 11 11 11 00          mov    $0x111111,%eax
  401044:       5d                      pop    %rbp
  401045:       c3                      ret

Re: GCC emits mov instead of lea

Posted: Thu Oct 10, 2024 8:58 pm
by Octocontrabass
korangar wrote: Thu Oct 10, 2024 8:41 amThe question is why the linker doesn't replace 0x0(%rip) with a constant (just like with the .elf), o replaces the MOV with LEA.
I wouldn't be surprised if those optimizations only work on ELF output. But why are you compiling position-independent code in the first place?