Moje odkazy
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
.
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"
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
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
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
).
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.
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).
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?
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)
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.jarKouká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…
Podle zdrojáku tam jexec
hledá kafe :-)
if (xhid == 0xcafe) { // found the JAR magic result = NULL; break; }
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 :-)
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?
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í.
Máš recht. Tj strašný jak člověk zapomíná, tohle jsem ještě před 8 lety věděl.