Java bez izvornog koda

Dijagnostika tihog pada, javap, dekompajler, podmetanje klase, agent/JDWP/attach + PowerShell za cmd korisnike

Beleške iz stvarnog slučaja (22. septembar 2026): stara Java 8 Swing aplikacija "samo capne" posle povezivanja na MySQL, bez ijedne poruke o grešci. Nađen je uzrok, popravljen bez izvornog koda, i program je pokrenut sa ispravljenom klasom, a da originalni JAR nije diran.

Imena programa, paketa i klasa su promenjena (anonimizovano). Tehnike i komande su iste kao u stvarnom slučaju.

0. Ceo slučaj u 8 rečenica

  1. Program se pokretao, povezivao na bazu i gasio sa izlaznim kodom 0, bez poruke.
  2. javaw.exe nema konzolu, pa je stack trace nestajao. Prvi korak je java.exe + log fajl.
  3. Agent (-javaagent) je pokazao da konekcija na bazu radi, a da se JVM gasi bez System.exit.
  4. Debug protokol (JDWP) + tracer su zabeležili svaki izuzetak, i onaj koji program proguta.
  5. Poslednji izuzetak pre gašenja: IllegalArgumentException: URI is not hierarchical u ImageLoader.getImage.
  6. javap je pokazao zašto: metoda uvek radi new File(url.toURI()), a za sliku iz JAR-a URL je jar:file:…!/…, koji File ne prihvata.
  7. Popravka: dekompajlirana klasa, izbačena dva nepotrebna reda, kompajlirano --release 8.
  8. Ispravljena klasa je stavljena ispred JAR-a na classpath. JVM učitava nju, a original ostaje netaknut.

1. PowerShell za nekoga ko zna cmd

PowerShell radi sa objektima, a ne sa tekstom. dir u cmd-u vraća tekst, a Get-ChildItem vraća listu fajl-objekata sa svojstvima (Name, Length, LastWriteTime…), koje zatim možeš da filtriraš i biraš. Zato je moćniji, ali i drugačiji.

Prevodni rečnik cmd → PowerShell

cmdPowerShell (puno ime)kratko (alias)napomena
set X=C:\a$X = 'C:\a'promenljiva počinje sa $
%X%$Xu stringu: "$X\fajl" (dvostruki navodnici)
set PATH=... / %PATH%$env:PATH = ... / $env:PATHpromenljive okruženja su $env:IME
dirGet-ChildItemdir, ls, gci-Recurse = dir /s
type fajlGet-Content fajltype, cat-Tail 40 = poslednjih 40 linija
findstr "tekst" fajlSelect-String -Path fajl -Pattern 'tekst'sls-Pattern je regex
copy a bCopy-Item a bcopy, cp-Force = prepiši
move a bMove-Item a bmove, mv
ren a bRename-Item a bren
mkdir a\b\cNew-Item -ItemType Directory -Force a\b\cmkdir-Force: ne buni se ako postoji
del fajlRemove-Item fajldel, rm
pushd x / popdPush-Location x / Pop-Locationpushd / popdisto kao u cmd-u
start "" prog.exeStart-Process prog.exestart-PassThru vraća proces (PID)
taskkill /PID 123Stop-Process -Id 123kill
echo %ERRORLEVEL%$LASTEXITCODEizlazni kod poslednjeg programa
prog > log.txt 2>&1istoisto kao u cmd-u
^ na kraju reda` `` (backtick) na kraju redanastavak komande u sledećem redu

Stvari kojih u cmd-u nema

& "C:\Program Files\x\prog.exe" arg1

& = pozovi ovo. Kad je putanja programa u navodnicima ili u promenljivoj, PowerShell je bez & tretira kao običan string i samo ga ispiše. Zato ćeš videti & "$T\jdk17\bin\java.exe" ....

Get-ChildItem out -Recurse -File | Select FullName, Length

| (pipeline) prosleđuje objekte. Select bira kolone (svojstva) koje hoćeš da vidiš.

Get-ChildItem out | Where-Object { $_.Length -gt 1MB }

Where-Object { ... } filtrira, a $_ je "trenutni objekat". -gt je "veće od" (-lt manje, -eq jednako, -like džoker *, -match regex). 1MB PowerShell razume kao broj.

'jednostruki $X'    # bukvalno: $X ostaje $X
"dvostruki $X"      # umetne vrednost promenljive

Navodnici: jednostruki su doslovni, dvostruki umeću promenljive. Za regex i putanje sa $ koristi jednostruke.

# komentar

Komentar je #, u cmd-u je to rem.


2. Alati (svi portabilni, ništa na sistemu)

AlatDeo čegaŠta radi
java.exeJDK/JREpokreće program
javac.exeJDKkompajlira .java u .class
javap.exeJDKdisasembler: .class → čitljive JVM instrukcije
jar.exeJDKpravi/čita/menja .jar (ZIP)
cfr.jarCFR 0.152dekompajler: .class → Java izvorni kod (približan)
cortiveo-agent.jarCortiveoagent: stablo prozora, System.exit, JDBC log, izuzeci
cortiveo-tracer.jarCortiveoJDWP tracer: svi izuzeci + mesto gde su progutani; Attach

Sve je u jednom folderu (tools\): JDK 8 za pokretanje starog programa, JDK 17 za alate, CFR. Sistemska Java se ne dira. Postavka na početku svake sesije:

$T   = 'E:\Work\AI\Cortiveo\tools'          # alati
$APP = 'C:\...\LegacyApp'                    # folder programa
$JAR = "$APP\app.jar"                        # "..." jer umećemo $APP
$W   = 'C:\...\patch-work'                   # radni folder za popravku

3. JAR je običan ZIP

& "$T\jdk17\bin\jar.exe" tf $JAR | Select-String 'ImageLoader|MANIFEST'

Ime klase = putanja u ZIP-u: engine.system.ImageLoader → engine/system/ImageLoader.class.

U META-INF/MANIFEST.MF su bitne dve linije:


4. Čitanje koda bez izvora: javap

& "$T\jdk17\bin\javap.exe" -cp $JAR -c -l -p engine.system.ImageLoader
InstrukcijaZnačenje
ldc "tekst"konstanta: stringovi su najbolji tragovi (SQL, imena fajlova, poruke)
invokevirtual, invokestaticpoziv metode
new X + invokespecial X."<init>"new X(...)
getstatic / putstaticčitanje / pisanje statičkog polja
poprezultat se baca (često: greška se ignoriše)
if…, gotogrananje
areturn / returnpovratak iz metode
Exception tabletry/catch: izuzetak tipa X između offseta from–to → skok na target

Šta smo pročitali: new File, pa URL.toURI(), pa File.<init>(URI) na glavnoj putanji, a Exception table hvata samo URISyntaxException. Bačen je IllegalArgumentException, dakle drugi tip, i on prolazi kroz catch.

Provera verzije bytecode-a:

& "$T\jdk17\bin\javap.exe" -v Klasa.class | Select-String 'major version'

52 = Java 8, 55 = 11, 61 = 17, 65 = 21.


5. Dekompajler i jedna nova stvar o Javi (labelirani blokovi)

& "$T\jdk17\bin\java.exe" -jar "$T\cfr.jar" $JAR `
    --jarfilter '^engine\.system\.ImageLoader$' --outputdir "$W\decompiled"

Dekompilovan kod nije original. CFR je vratio ovo:

block8: {
    url = c.getResource("/resources/" + name);
    try {
        if (url != null) break block8;
        return null;
    } catch (Exception e) { ... }
}

block8: je labela, a break block8; iskače iz tog bloka. Java to ima od prve verzije, ali ljudi to retko pišu, najčešće na petljama:

spolja:
for (Red r : tabela) {
    for (Celija c : r) {
        if (c.equals(trazeno)) break spolja;   // izlazi iz OBE petlje
    }
}

Bytecode nema if/try strukture, samo skokove, pa dekompajler mora da ih prevede nazad u Javu i za to često koristi labele. U originalu je try obuhvatao ceo deo metode, a CFR ga je prikazao drugačije. Pravilo: dekompilovan kod služi za razumevanje, a kad je nešto sporno, merodavan je javap ili tracer.


6. Recept: program "samo capne"

  1. java.exe, a ne javaw.exe, i sve u fajl: java -jar app.jar > run.log 2>&1.
  2. Ima li novog hs_err_pid*.log u radnom folderu? Ako ima, pala je sama JVM (native). Gledaj # Problematic frame: i Java frames:.
  3. Radni folder mora biti isti kao u produkciji (pushd u folder programa), jer program konfiguraciju čita iz tekućeg foldera.
  4. Agent → diag-PID.log: postoji li System.exit stack, JDBC greška, neuhvaćen izuzetak?
  5. Ako nema ničega: JDWP + tracer (ili Eclipse exception breakpoint) → poslednji izuzetak pre gašenja.
  6. javap -c -l na klasi i liniji koju je tracer pokazao → razumeš zašto.
  7. Popravka: način pokretanja (§9), podmetnuta klasa (§10) ili izmena izvora.

7. Agent: -javaagent

java "-javaagent:C:\...\cortiveo-agent.jar=out=C:\...\out,delay=5000" -jar app.jar

Agent je JAR koji JVM učita pre main()-a, u isti proces. Program se ne menja. Opcije posle =, odvojene zarezom: out= folder, delay= ms do prvog snimka, interval= ms između snimaka, count= koliko (0 = neograničeno), diag=false isključuje dijagnostiku.

Šta daje:

Primer (anonimizovano):

MainFrame [window] "Štampanje obrazaca"
    extends AppFrame -> CascadeFrame -> BaseFrame -> JFrame
  MenuBar$LocalMenuItem [menu-item] "Fakture"
      action DbTable::app.forms.Fakture::      ← klasa koja se otvara iz ovog menija

U stvarnom slučaju jedan snimak je dao 40 stavki menija → 31 klasu → 31 .java fajl, tj. kompletnu mapu programa. Pokazao je i da stavka "… 2025." otvara klasu sa "2021" u imenu. To je svesna konvencija (kod manjih izmena ime klase ostaje), ali je dobro što se vidi.


8. Debug protokol (JDWP): svi izuzeci, i progutani

Svaka Java od 1.4 ima ugrađen debug server:

java -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=127.0.0.1:5005 -jar app.jar

Opcija A: tracer (automatski log u fajl):

& "$T\jdk17\bin\java.exe" -jar cortiveo-tracer.jar port=5005 "out=$W\exceptions.log" "app=engine.,app."

app= su prefiksi paketa programa; izuzeci koji ne dodiruju njihov kod su šum i preskaču se. Svaki unos ima thrown: (gde je nastao), caught: (gde je progutan) i stack sa linijama (> = kod programa).

Opcija B: Eclipse (isti protokol):

  1. pokreni program sa -agentlib:jdwp=…;
  2. Run → Debug Configurations → Remote Java Application, localhost, port 5005;
  3. Run → Add Java Exception Breakpoint → tip izuzetka, uključi Caught i Uncaught;
  4. Debug: staje tačno na liniji gde izuzetak nastaje. Radi i bez izvora.

9. Classpath: -jar protiv -cp

PokretanjeGde JVM traži klase i resurse
java -jar app.jaru app.jar, pa u Class-Path iz manifesta. -cp se ignoriše!
java -cp "A;B;C" app.Mainredom A, B, C, i prvi pogodak pobeđuje

(; je separator na Windows-u, : na Linux-u. Sa -cp glavnu klasu navodiš sam.)

Privremeno rešenje pre popravke: resurse izvući iz JAR-a na disk i staviti folder ispred JAR-a (-cp ".;app.jar"). Tako slike dobijaju file: URL i new File(URI) prolazi. Isto se dešava kad se program pokreće iz Eclipse bin\ foldera, i zato je "radio samo iz Eclipse-a".


10. Podmetanje ispravljene klase: ceo postupak

Princip: isto pravilo iz §9 (prvi na classpath-u pobeđuje) važi i za klase. Tvoja engine/system/ImageLoader.class u folderu ispred JAR-a zamenjuje originalnu, a JAR se ne dira.

Pravila: isti paket, isto ime klase, iste javne metode sa istim potpisima.

Korak 1: dekompajliraj

(komanda iz §5)

Korak 2: nađi grešku

Dva reda new File(url.toURI()): jedan na normalnoj putanji (rezultat se ni ne koristi), drugi u catch bloku (dijagnostika koja i sama pada istom greškom).

Korak 3: ispravljena kopija u folderu koji prati paket

New-Item -ItemType Directory -Force "$W\patch-src\engine\system" | Out-Null
Move-Item "$W\decompiled\engine\system\ImageLoader.java" "$W\patch-src\engine\system\"

Izmena: vrati Image odmah; u catch bloku ispiši url kakav jeste, bez new File(...).

Korak 4: kompajliraj za Javu 8

& "$T\jdk17\bin\javac.exe" --release 8 -encoding UTF-8 `
    -cp $JAR `
    -d "$W\patch" `
    "$W\patch-src\engine\system\ImageLoader.java"
DeoZašto
--release 8bytecode i API se proveravaju prema Javi 8 (vidi §12)
-encoding UTF-8naša slova u izvoru ostaju ispravna
-cp $JARkompajler vidi ostatak programa (neophodno kad klasa koristi druge klase programa)
-d "$W\patch"izlaz; javac sam pravi patch\engine\system\ImageLoader.class

Provera: javap -v … | Select-String 'major version' → 52.

Korak 5: pokreni sa patch prvim i dokaži koja klasa je učitana

Push-Location $APP
& "$T\jdk8\bin\java.exe" -verbose:class `
    -cp "$W\patch;app.jar;." `
    app.Main > "$W\run-verbose.log" 2>&1
Pop-Location
Select-String -Path "$W\run-verbose.log" -Pattern 'engine\.system\.ImageLoader '

Očekivano:

[Loaded engine.system.ImageLoader from file:/C:/.../patch-work/patch/]

Ako piše ...app.jar, učitan je original, što znači da je redosled u -cp pogrešan.

Korak 6: dokaz da je popravka prava

Isključi zaobilaznicu iz §9 (Rename-Item resources resources.off) i pokreni ponovo. Ako program radi, popravka je stvarna.

Ograničenja i alternative


11. Attach: agent u program koji već radi

& "$T\jdk17\bin\java.exe" -cp cortiveo-tracer.jar io.cortiveo.tracer.Attach list
& "$T\jdk17\bin\java.exe" -cp cortiveo-tracer.jar io.cortiveo.tracer.Attach <PID> C:\...\cortiveo-agent.jar "out=C:\...\out,delay=1000,diag=false"

Dve lekcije iz prakse:

  1. Ako program već ima stariju verziju agenta, attach pokreće stari kod: klase sa istim imenom se u jednom JVM-u ne učitavaju dvaput. Tada: restart bez agenta, pa attach.
  2. Klijent iz Jave 9+ prema cilju na Javi 8 prijavi AgentLoadException: 0, iako je agent uspešno učitan (Java 8 vraća "0" = uspeh). Cortiveo Attach to već ignoriše.

12. Zamka: kompajliranje novim JDK-om za staru Javu

KakoRezultat na Javi 8
-source 8 -target 8kompajlira se bez greške, ali puca u radu: NoSuchMethodError (npr. ByteBuffer.flip())
--release 8ispravno: proverava i jezik i API prema pravoj Javi 8

U Eclipse-u / IntelliJ-u: uključiti opciju "use --release". "Compiler compliance 1.8" nije dovoljno.


13. Šalabahter

# sadržaj JAR-a / manifest
& "$T\jdk17\bin\jar.exe" tf $JAR
# instrukcije + linije
& "$T\jdk17\bin\javap.exe" -cp $JAR -c -l -p paket.Klasa
# dekompajliraj jednu klasu
& "$T\jdk17\bin\java.exe" -jar "$T\cfr.jar" $JAR --jarfilter '^paket\.Klasa$' --outputdir "$W\decompiled"
# kompajliraj za Javu 8
& "$T\jdk17\bin\javac.exe" --release 8 -encoding UTF-8 -cp $JAR -d "$W\patch" <fajl.java>
# pokreni sa podmetnutom klasom + dokaz
& "$T\jdk8\bin\java.exe" -verbose:class -cp "$W\patch;app.jar;." app.Main > run.log 2>&1
# agent (dijagnostika + snimak)
java "-javaagent:cortiveo-agent.jar=out=out,delay=5000" -jar app.jar
# debug protokol + tracer
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=127.0.0.1:5005 -jar app.jar
& "$T\jdk17\bin\java.exe" -jar cortiveo-tracer.jar port=5005 out=exceptions.log app=moj.paket.
# attach na pokrenut program
& "$T\jdk17\bin\java.exe" -cp cortiveo-tracer.jar io.cortiveo.tracer.Attach list

Izvor: java-bez-izvora.md