FK~

Moje odkazy

Ostatní odkazy

Close Windows
Nenajdete mě na Facebooku ani Twitteru
Rozpad EU
Jsem členem FSF
There Is No Cloud …just other people's computers.
Sane software manifesto / Manifest příčetného softwaru

binfmt_misc: spouštíme javovské programy podobně jako nativní binárky

vydáno: 20. 6. 2015 20:03, aktualizováno: 20. 6. 2015 21:57

Programy napsané v jazyce Java se překládají do tzv. bajtkódu a pro jejich spuštění je – na rozdíl od nativních binárek jako ELF – potřeba běhové prostředí (JRE). Programy v Javě tedy spouštíme pomocí příkazů typu:

java NázevTřídy

# nebo častěji:
java -jar program.jar

Linux (jádro) ale obsahuje mechanismus, umožňující spouštět programy v různých (interpretovaných) jazycích stejně jako nativní binárky – stačí tedy souboru nastavit spustitelný příznak (chmod +x) a spustit bo pouhým zadáním jeho názvu – např. program.jar.

Příklad

Vyzkoušíme si to hned na praktickém příkladu. Vytvoříme si program v Javě a uložíme ho do souboru Test.java:

import java.util.Arrays;

public class Test {
	public static void main(String[] args) {
		System.out.println(Arrays.toString(args));
	}
}

Program zkompilujeme

$ javac Test.java

A zabalíme do JARu (javovský archivní formát odvozený od ZIPu):

$ jar cfe test.jar Test Test.class

JARy se běžně spouští příkazem

$ java -jar test.jar

My ho ale můžeme spustit stejně, jako by to byla nativní aplikace (psaná např. v C++) nebo skript v Bashi. Stačí nastavit spustitelný příznak:

$ chmod +x program.jar

A jednoduše spustíme:

$ ./program.jar

Program si samozřejmě můžeme dát do cesty ($PATH) a pak není potřeba zadávat ./ a dokonce ho můžeme i přejmenovat – přípona .jar není nutná a můžeme si spouštět třeba:

$ náš-program "nějaké" "parametry"

Jak binfmt_misc funguje

binfmt_misc je obecný mechanismus, který umožňuje spouštět binárky (obecně soubory) v libovolném spustitelném formátu. Protože se nejedná o nativní spustitelný soubor (ELF), který by šel vykonat přímo procesorem, musí se nějaký interpret použít. Ovšem pro uživatele je to transparentní – pouze spustí daný soubor (stejně jako nativní binárku) a o zbytek už se postará operační systém – binfmt_misc vybere vhodný interpret a předá mu cestu k souboru jako parametr.

Interpret se vybere

  • buď na základě magického čísla (posloupnost bajtů na začátku souboru nebo na určité pozici)
  • nebo na základě přípony souboru

Pro javovské JARy se používají bajty 504b0304 – proto nezáleží na příponě a můžete si program klidně přejmenovat.

$ cat /proc/sys/fs/binfmt_misc/jar 
enabled
interpreter /usr/bin/jexec
flags: 
offset 0
magic 504b0304

Můžeme si ověřit, že tam ty bajty skutečně jsou:

$ hexdump -C program.jar | head -n 1
00000000  50 4b 03 04 0a 00 00 08  00 00 6b 04 b9 46 00 00  |PK........k..F..|

Mimochodem: stejným způsobem identifikuje typy souborů příkaz file:

$ file program.jar
program.jar: Java archive data (JAR)

Jak je vidět výše, JAR se bude spouštět přes jexec. Pokud máte v systému nainstalovaných více JRE, zvolte si požadovanou verzi – v Debianu/Ubuntu příkazem:

$ update-alternatives --config jexec

Registrace vlastního spustitelného formátu

Vaše distribuce GNU/Linuxu pravděpodobně obsahuje nějaké předinstalované spustitelné typy, ale nejlepší na tom je, že si můžeme přidat i typy vlastní. Vyzkoušíme si to na příkladu – jako root spustíme:

$ echo ":zzzTest:M::zzz_test::/usr/bin/wc:" > /proc/sys/fs/binfmt_misc/register

Tím jsme si zaregistrovali formát jménem zzzTest, který bude rozpoznán tak, že soubor na začátku obsahuje magické číslo zzz_test. A jako „interpret“ jsme použili program wc – ten sice nic ve skutečnosti neinterpretuje (jen počítá řádky, slova a bajty), ale na vyzkoušení to stačí.

Vytvoříme si „program“ v našem nově definovaném formátu a spustíme ho:

$ echo "zzz_test nějaký program"> testovací-program
$ chmod +x testovací-program 
$ ./testovací-program
 1  3 26 ./testovací-program

Vypsalo se nám totéž, jako bychom zadali:

$ wc ./testovací-program

Z pohledu uživatele je ale soubor testovací-program normální spustitelná binárka (ne žádný skript nebo datový soubor).

Stačí si tedy zvolit dostatečně jedinečné magické číslo (nebo příponu) a místo wc zaregistrovat skutečný interpret a máme vlastní opravdový spustitelný formát binárek!

Když po sobě chceme uklidit a daný formát zase odregistrovat, zadáme:

$ echo -1 > /proc/sys/fs/binfmt_misc/zzzTest

n.b. v distribucích jako Debian nebo Ubuntu máme příkaz update-binfmts, abychom nemuseli používat nízkoúrovňové API jádra (zápisy do souborů v /proc).

Vsuvka: binfmt_script – shebang (hashbang)

Pro spouštění skriptů se používá binfmt_script a shebang, což je ono známé #!/bin/sh nebo třeba #!/usr/bin/env perl na začátku souboru. Cesta k interpretu (nebo jeho název) je tedy uvedena přímo ve skriptu.

Na rozdíl od binfmt_misc se u skriptů ignoruje setuid/setgid bit (velké bezpečnostní riziko), takže skripty nemůžete spouštět s právy vlastníka (typicky roota). V případě binfmt_misc lze podporu setuid/setgid zapnout při registraci příslušného formátu.

Využití

Díky binfmt_misc si můžeme snadno (aniž bychom museli něco programovat) definovat vlastní spustitelný formát pro náš jazyk (emulátor, běhové prostředí, formát atd.), a dosáhnout tak vyšší integrace s operačním systémem – pro uživatele je to normální spustitelná binárka (včetně takové funkcionality, jako je setuid/setgid bit).

binfmt_misc se skvěle hodí pro různé emulátory a běhová prostředí. Ve své distribuci pravděpodobně najdete už hotové konfigurace pro DOSEMU, Wine nebo Qemu (vedle té Javy, kterou jsme si ukázali na začátku).

Odkazy a zdroje:

Témata: [GNU/Linux] [Java]

Komentáře čtenářů


Tomáš Crhonek, 21. 6. 2015 09:32, jexec Exec format error [odpovědět]

Nevím přesně kdy, ale spouštění java programů mi přestalo fungovat:

$ ./Minecraft.jar 
invalid file (bad magic number): Exec format error

Přitom java -jar Minecraft.jar to normálně spustím.

Magic nuber v souboru je ok, /proc/sys/fs/binfmt_misc/jar je enabled se stejným magic number.

Jediný rozdíl je, že jexec to nespustí (s výše uvedenou chybou), zatímco java -jar ano.

JRE mám jen jedno, oba příkazy jsou z toho stejného:

$ sudo update-alternatives --config jexec
There is only one alternative in link group jexec (providing /usr/bin/jexec): /usr/lib/jvm/java-7-openjdk-amd64/jre/lib/jexec
Nothing to configure.


$ sudo update-alternatives --config java
There is only one alternative in link group java (providing /usr/bin/java): /usr/lib/jvm/java-7-openjdk-amd64/jre/bin/java
Nothing to configure.

Netušíš, kde by mohla být zrada?


Franta, 21. 6. 2015 12:11, jexec [odpovědět]

To je dost zvláštní, protože podle strace pouští ten jexec ve skutečnosti java -jar …, resp.

execve("/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java", ["/usr/lib/jvm/java-8-openjdk-amd6"..., "-jar"], [/* 64 vars */]) = 0

Asi bych se kouknul do toho strace, co se tam dělo. Nebo nahraj někam ten JAR, můžu to vyzkoušet.

Tady někdo píše o stejné chybě: Ubuntu binfmt java is broken on my 8.10 LTS. Tak leda, že by tam byla nějaká regrese. Je fakt, že já jsem to zkoušel na Javě 8, kde to může být už zase opravené. Ale stejně by mě zajímal ten strace :-)

BTW: jexec s nějakým jiným JARem ti funguje? (třeba ten příklad z tohoto článku)


Tomáš Crhonek, 21. 6. 2015 13:37, jexec [odpovědět]

Ano, tvůj test jde spustit ./test.jar (a samozřejmně i přes jexec).

Strace běhu strace jexec Minecraft.jar je zde http://pastebin.com/3uQVrYQx.

Oproti strace jexec test.jar se to liší na 41 řádku:

u testu:execve("/usr/lib/jvm/java-7-openjdk-amd... jak jsi psal

u mě to vede na generovaní výše uvedené chybové hlášky.

Soubor Minecraft.jar je spuštěč hry Minecraft (nikoliv hra samotná) a je to z oficiálního webu Minecraftu. Zde jsem nahrál soubor tak, jak je u mě na disku: Minecraft.jar

Franta, 21. 6. 2015 15:25, Formát JAR a magická čísla [odpovědět]

Koukám, že to 504b0304 asi nebude celé magické číslo. Jako JAR to totiž nerozpozná ani příkaz file a považuje to za obecný ZIP:

$ file *.jar
Minecraft.jar:   Zip archive data, at least v1.0 to extract
program.jar:     Java archive data (JAR)

Zkusil bych to přebalit:

mkdir temp
cd temp
jar xf ../Minecraft.jar
jar cfe ../Minecraft-2.jar net.minecraft.bootstrap.Bootstrap *

Pak je vidět, že se JARy liší:

$ hexdump -C Minecraft.jar | head -n 1
00000000  50 4b 03 04 0a 00 00 08  08 00 92 74 ec 42 00 00  |PK.........t.B..|

$ hexdump -C Minecraft-2.jar | head -n 1
00000000  50 4b 03 04 14 00 08 08  08 00 5d 75 d5 46 00 00  |PK........]u.F..|

$ hexdump -C program.jar | head -n 1
00000000  50 4b 03 04 14 00 08 08  08 00 40 62 d5 46 00 00  |PK........@b.F..|

Původní měl na pátém bajtu 0a a ten nový 14. A file už to taky jako JAR rozpozná:

$ file *.jar
Minecraft.jar:   Zip archive data, at least v1.0 to extract
Minecraft-2.jar: Java archive data (JAR)
program.jar:     Java archive data (JAR)

BTW: některé JARy mi jdou procházet v Midnight Commanderu a některé ne (musím si na ně vytvořit symbolický odkaz s příponou .zip), což souvisí s tímhle a Minecraft zjevně není jediný, kdo má v JARech 0a místo 14. Tohle musím ještě vyzkoumat…


Franta, 21. 6. 2015 16:13, CAFE [odpovědět]

Podle zdrojáku tam jexec hledá kafe :-)

if (xhid == 0xcafe) {
	// found the JAR magic
	result = NULL;
	break;
}

Franta, 21. 6. 2015 17:06, JAR verze 1.2 [odpovědět]

Ještě jsem tam našel poznámku:

NOTE: You must use 1.2 jar to build your jar files. The system doesn't seem to pick up 1.1 jar files.

Takže to bude nejspíš ono. Já snad začnu přispívat do OpenJDK :-)


Tomáš Crhonek, 22. 6. 2015 20:10, jexec [odpovědět]

Aha, takže oni to balí ve starší verzi jaru (1.1)? Asi.

Díky za prozkoumání problému, můžu si to tedy kdykoliv přebalit.

Btw, jak najít vstupní třídu? Ten bootstrap jsi znal, nebo je nějaký způsob, jak najít startovací třídu?


Franta, 22. 6. 2015 23:33, JAR a hlavní třída [odpovědět]

Název hlavní třídy se píše do souboru META-INF/MANIFEST.MF do pole Main-Class– podle toho Java pozná, kterou třídu má spustit. Nemusí tam být nic, když je ten JAR jen knihovna a sám se nespouští.


Tomáš Crhonek, 23. 6. 2015 09:40, JAR a hlavní třída [odpovědět]

Máš recht. Tj strašný jak člověk zapomíná, tohle jsem ještě před 8 lety věděl.

Přidat komentář

reagujete na jiný komentář (zrušit)
jméno nebo přezdívka
název příspěvku
webová stránka, blog
e-mailová adresa
nápověda: možnosti formátování
ochrana proti spamu a špatným trollům

Náhled komentáře