GCC emits mov instead of lea

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.
SomePerson
Posts: 7
Joined: Sun Sep 15, 2024 8:05 am
Libera.chat IRC: SomePerson

GCC emits mov instead of lea

Post 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?
nullplan
Member
Member
Posts: 1780
Joined: Wed Aug 30, 2017 8:24 am

Re: GCC emits mov instead of lea

Post 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?
Carpe diem!
SomePerson
Posts: 7
Joined: Sun Sep 15, 2024 8:05 am
Libera.chat IRC: SomePerson

Re: GCC emits mov instead of lea

Post by SomePerson »

ISR0 is a function declared like this:

Code: Select all

void ISR0();
User avatar
iansjack
Member
Member
Posts: 4700
Joined: Sat Mar 31, 2012 3:07 am
Location: Chichester, UK

Re: GCC emits mov instead of lea

Post 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.
SomePerson
Posts: 7
Joined: Sun Sep 15, 2024 8:05 am
Libera.chat IRC: SomePerson

Re: GCC emits mov instead of lea

Post by SomePerson »

Yes and the same problem also occurs with functions that I can call that are in another file.
User avatar
iansjack
Member
Member
Posts: 4700
Joined: Sat Mar 31, 2012 3:07 am
Location: Chichester, UK

Re: GCC emits mov instead of lea

Post by iansjack »

I think, as null plan says, that we need to see the code.
SomePerson
Posts: 7
Joined: Sun Sep 15, 2024 8:05 am
Libera.chat IRC: SomePerson

Re: GCC emits mov instead of lea

Post 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;
}
sebihepp
Member
Member
Posts: 184
Joined: Tue Aug 26, 2008 11:24 am
GitHub: https://github.com/sebihepp

Re: GCC emits mov instead of lea

Post by sebihepp »

Try &ISR0 instead of ISR0
SomePerson
Posts: 7
Joined: Sun Sep 15, 2024 8:05 am
Libera.chat IRC: SomePerson

Re: GCC emits mov instead of lea

Post 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);
Octocontrabass
Member
Member
Posts: 5549
Joined: Mon Mar 25, 2013 7:01 pm

Re: GCC emits mov instead of lea

Post by Octocontrabass »

How are you compiling and linking this code? That looks a bit like a PIC relocation that didn't get linked correctly.
SomePerson
Posts: 7
Joined: Sun Sep 15, 2024 8:05 am
Libera.chat IRC: SomePerson

Re: GCC emits mov instead of lea

Post 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).
Octocontrabass
Member
Member
Posts: 5549
Joined: Mon Mar 25, 2013 7:01 pm

Re: GCC emits mov instead of lea

Post 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).
SomePerson
Posts: 7
Joined: Sun Sep 15, 2024 8:05 am
Libera.chat IRC: SomePerson

Re: GCC emits mov instead of lea

Post by SomePerson »

Thank you.
korangar
Posts: 3
Joined: Wed Oct 09, 2024 5:52 am

Re: GCC emits mov instead of lea

Post 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
Octocontrabass
Member
Member
Posts: 5549
Joined: Mon Mar 25, 2013 7:01 pm

Re: GCC emits mov instead of lea

Post 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?
Post Reply