error: bp cannot be used in asm here

Programming, for all ages and all languages.
Post Reply
nullplan
Member
Member
Posts: 1894
Joined: Wed Aug 30, 2017 8:24 am

error: bp cannot be used in asm here

Post by nullplan »

Hi all,

today I got burned on GCC a little, and I think venting might help me sleep later.

Today I did a few build tests with my libc implementation, and I got the above curious error message when compiling this function for i386:

Code: Select all

static inline long __syscall6(long nr, long a, long b, long c, long d, long e, long f)
{
  register long eax __asm__("eax") = nr;
  register long ebx __asm__("ebx") = a;
  register long ecx __asm__("ecx") = b;
  register long edx __asm__("edx") = c;
  register long esi __asm__("esi") = d;
  register long edi __asm__("edi") = e;
  register long ebp __asm__("ebp") = f;
  __asm__ volatile("calll *%%gs:16" : "+r"(eax) : "r"(ebx), "r"(ecx), "r"(edx), "r"(esi), "r"(edi), "r"(ebp) : "memory");
  return eax;
}
What is curious is that I only get an error if optimization is disabled. Googling the message turned up that if GCC decides to compile the function with frame pointer, then EBP is off limits. Of course, on i386, GCC works without frame pointers most of the time, as long as any optimization is on, really. So it will accept the above code only conditional on optimization level.

I have now added a compile test and a workaround: If the compiler is found to not like ebp in assembler, then the above assembly is turned instead into

Code: Select all

  __asm__ volatile("pushl %6; xchgl %%ebp, (%%esp); calll *%%gs:16; popl %%ebp" : "+r"(eax) : "r"(ebx), "r"(ecx), "r"(edx), "r"(esi), "r"(edi), "g"(f) : "memory");
which the compiler thankfully accepts. Though I do wonder if my test will eventually fail when the compiler decides to enable frame pointers for only some of the time.

I would be somewhat willing to forgive the quirkiness if the compiler was a little bit older. But no, this is with GCC 12, the thing that's currently shipped with Debian.
Carpe diem!
Octocontrabass
Member
Member
Posts: 5817
Joined: Mon Mar 25, 2013 7:01 pm

Re: error: bp cannot be used in asm here

Post by Octocontrabass »

nullplan wrote: Wed Jun 04, 2025 12:32 pm

Code: Select all

  register long eax __asm__("eax") = nr;
  register long ebx __asm__("ebx") = a;
  register long ecx __asm__("ecx") = b;
  register long edx __asm__("edx") = c;
  register long esi __asm__("esi") = d;
  register long edi __asm__("edi") = e;
Why all of this instead of using the "a", "b", "c", "d", "S", and "D" constraints in your inline asm?
nullplan wrote: Wed Jun 04, 2025 12:32 pm

Code: Select all

"calll *%%gs:16"
The pointer to the system call wrapper is thread-local? Is that how you switch between INT and SYSENTER at runtime? (I assume you're not using SYSCALL, since it would clobber ECX and you have no free registers.)
nullplan wrote: Wed Jun 04, 2025 12:32 pm

Code: Select all

xchgl %%ebp, (%%esp);
This instruction behaves as if it always has a LOCK prefix, which has a performance penalty.
nullplan wrote: Wed Jun 04, 2025 12:32 pmThough I do wonder if my test will eventually fail when the compiler decides to enable frame pointers for only some of the time.
Depending on where that function is inlined, it could happen.
nullplan wrote: Wed Jun 04, 2025 12:32 pmI would be somewhat willing to forgive the quirkiness if the compiler was a little bit older. But no, this is with GCC 12, the thing that's currently shipped with Debian.
I'd guess it's a compromise between people who want to use alloca() and people who want to use as many registers as possible in inline asm.
nullplan
Member
Member
Posts: 1894
Joined: Wed Aug 30, 2017 8:24 am

Re: error: bp cannot be used in asm here

Post by nullplan »

Octocontrabass wrote: Wed Jun 04, 2025 10:02 pm Why all of this instead of using the "a", "b", "c", "d", "S", and "D" constraints in your inline asm?
To be in line with the other syscall wrappers for the other architectures. This syntax works everywhere, but not everywhere has constraints for the necessary registers. Plus there's no constraint for EBP.
Octocontrabass wrote: Wed Jun 04, 2025 10:02 pm The pointer to the system call wrapper is thread-local? Is that how you switch between INT and SYSENTER at runtime? (I assume you're not using SYSCALL, since it would clobber ECX and you have no free registers.)
I might have added that this is for a Linux libc. Yes, the sysinfo pointer is thread-local, since that is the simplest way to have an indirect call that is also position-independent. If I went the normal route, I would have to set up a GOT pointer in every function that makes a syscall, and for many functions, this is just overkill.

Which instruction the function uses in the end is something I don't have to care about. But it seems like it uses SYSCALL in long compatibility mode, though it saves away ECX on the stack. Linux only defines that if it gives me an AT_SYSINFO aux header, then I can call the referenced function in place of doing "int $0x80", and it does the rest.
Octocontrabass wrote: Wed Jun 04, 2025 10:02 pm I'd guess it's a compromise between people who want to use alloca() and people who want to use as many registers as possible in inline asm.
They can just save and restore EBP around the snippet, just as they do for all other registers that are in use. But no, I have to special-case this. Only consolation is that I control the functions this is used in (except if someone uses LTO, of course, but why would someone use LTO for an obsolete platform?) and I don't use alloca() anywhere, nor do I throw any exceptions. I do use VLAs in some places, but they don't do six-argument syscalls.
Carpe diem!
Octocontrabass
Member
Member
Posts: 5817
Joined: Mon Mar 25, 2013 7:01 pm

Re: error: bp cannot be used in asm here

Post by Octocontrabass »

nullplan wrote: Thu Jun 05, 2025 8:34 amThey can just save and restore EBP around the snippet, just as they do for all other registers that are in use.
They can't if they're relying on EBP to function as one of the operands.
Post Reply