Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Loaders rewrite argv[0] for old binaries #1170

Merged
merged 4 commits into from
May 8, 2024
Merged

Conversation

mrdomino
Copy link
Collaborator

@mrdomino mrdomino commented May 6, 2024

For this to work, a loader has to be able to tell the difference between
an ‘old’ and a ‘new’ binary. This is achieved via a repurposing of ELF’s
e_flags field. We previously tried to use the padding in e_ident for it,
but binutils was resetting it to zero in e.g. strip.

This introduces one new ELF flag for cosmopolitan binaries. It is called
`EF_APE_MODERN`, and it is bit 1.

It should now be safe to install the ape loader binfmt registration with
the `P` flag.

For this to work, a loader has to be able to tell the difference between
an ‘old’ and a ‘new’ binary. This is achieved via a repurposing of ELF’s
e_flags field. We previously tried to use the padding in e_ident for it,
but binutils was resetting it to zero in e.g. strip.

This introduces one new ELF flag for cosmopolitan binaries. It is called
`EF_APE_MODERN`, and it is bit 1.

It should now be safe to install the ape loader binfmt registration with
the `P` flag.
@mrdomino
Copy link
Collaborator Author

mrdomino commented May 7, 2024

Tested on XnuSilicon and Linux x86_64. A printargs without the new flag gets its argv[0] changed, whereas a printargs with the new flag gets it preserved.

The `ape -` variant with no arguments provides no way to shim in argv[0]
so the loader had no way to tell the binary what its path was. This is a
long-standing bug that looks like it has existed for as long as `ape -`,
and old loaders would, I guess, just overwrite the sentinel NULL that is
by convention the beginning and the end of a zero-length argv.

From the vantage point of TryElf I can't see a really good solution so I
just check for argc of 0 and let things be weird if a binary is run like
that.

This is only relevant with older binaries (as of jart#1170, those that don't
set `EF_APE_MODERN`.)
ape/ape-m1.c Outdated Show resolved Hide resolved
We choose 0x101ca75, "lol cat 5".

Also makes the argc check more explicit and moves the flags to their own
section.
Copy link
Owner

@jart jart left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@ghaerr
Copy link
Sponsor Contributor

ghaerr commented May 7, 2024

Hello @jart, Hello @mrdomino,

Shouldn't we choose a different magic number

I had noticed this before, but didn't comment. It seems the ef_flags field is quite CPU-dependent, but for ARM, this field appears to be a bit mask, rather than a magic number. For instance, in <elf.h>:

/* ARM specific declarations */

/* Processor specific flags for the ELF header e_flags field.  */
#define EF_ARM_RELEXEC          0x01
#define EF_ARM_HASENTRY         0x02
#define EF_ARM_INTERWORK        0x04
#define EF_ARM_APCS_26          0x08
#define EF_ARM_APCS_FLOAT       0x10
#define EF_ARM_PIC              0x20
#define EF_ARM_ALIGN8           0x40 /* 8-bit structure alignment is in use */
#define EF_ARM_NEW_ABI          0x80
#define EF_ARM_OLD_ABI          0x100
#define EF_ARM_SOFT_FLOAT       0x200
#define EF_ARM_VFP_FLOAT        0x400
#define EF_ARM_MAVERICK_FLOAT   0x800

#define EF_ARM_ABI_FLOAT_SOFT   0x200   /* NB conflicts with EF_ARM_SOFT_FLOAT */
#define EF_ARM_ABI_FLOAT_HARD   0x400   /* NB conflicts with EF_ARM_VFP_FLOAT */


/* Other constants defined in the ARM ELF spec. version B-01.  */
/* NB. These conflict with values defined above.  */
#define EF_ARM_SYMSARESORTED    0x04
#define EF_ARM_DYNSYMSUSESEGIDX 0x08
#define EF_ARM_MAPSYMSFIRST     0x10
#define EF_ARM_EABIMASK         0XFF000000

/* Constants defined in AAELF.  */
#define EF_ARM_BE8          0x00800000
#define EF_ARM_LE8          0x00400000

#define EF_ARM_EABI_VERSION(flags)      ((flags) & EF_ARM_EABIMASK)
#define EF_ARM_EABI_UNKNOWN     0x00000000
#define EF_ARM_EABI_VER1        0x01000000
#define EF_ARM_EABI_VER2        0x02000000
#define EF_ARM_EABI_VER3        0x03000000
#define EF_ARM_EABI_VER4        0x04000000
#define EF_ARM_EABI_VER5        0x05000000

To be actually safe, shouldn't our magic number when masked at least be limited to the unused bits in the above description? IMO, there would be a higher probability of not running into an intersection, which is what we're trying to accomplish with this change? If that were agreed, then a single masked bit might be best. Hopefully I'm not missing something here.

Thank you!

@mrdomino
Copy link
Collaborator Author

mrdomino commented May 7, 2024

I mean, empirically, it works fine on my raspberry pi with the value we have now. None of these seem to actually be used; the linux kernel just straight-up ignores it.

@mrdomino mrdomino merged commit 7d31fc3 into jart:master May 8, 2024
0 of 10 checks passed
@mrdomino mrdomino deleted the e_flags branch May 8, 2024 00:42
@jart
Copy link
Owner

jart commented May 8, 2024

We'll find out in a moment if FreeBSD ARM cares about once I run it on fleet.

@ghaerr
Copy link
Sponsor Contributor

ghaerr commented May 8, 2024

Searching FreeBSD source for EF_ARM_ shows that the EF_ARM_EABI_VER4 flag is used as a minimum required version number when the machine is configured for EABI support (Unknown whether EABI support is used or ON by default). The current magic word intersects with the EABI mask and would force the ARM EABI to be version 1.

Otherwise, it appears that the readelf utility reads 16 or so of the EF_ARM_ flags, for which the magic word intersects.

Does all this matter? Probably not, just an FYI.

@mrdomino
Copy link
Collaborator Author

mrdomino commented May 8, 2024

I guess if needs be we can always just shift to the top half of the word.

@mrdomino
Copy link
Collaborator Author

mrdomino commented May 8, 2024

There are plenty of other ways of signaling that we are a new-school binary, just this seems like one of the most convenient if we can get away with it.

@ghaerr
Copy link
Sponsor Contributor

ghaerr commented May 8, 2024

A quick look at RPi source seems to show most of the EF_ARM_ flags were used in 32-bit ARM, except for EF_ARM_EABI_MASK being checked on 64-bit only when CONFIG_COMPAT enabled. I am guessing many of these flags were created earlier during the 32-bit ARM development and their use dropped on modern 64-bit CPUs.

if needs be we can always just shift to the top half of the word.

Since Cosmo doesn't care about 32-bit binaries, ORing in 0x100000000 would seem an easy way of ignoring all the other bits while remaining compatible, should any of this matter for Cosmo-created APE binaries typically run on desktops.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants