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.
javaw.exe nema konzolu, pa je stack trace nestajao. Prvi korak je java.exe + log fajl.-javaagent) je pokazao da konekcija na bazu radi, a da se JVM gasi bez System.exit.IllegalArgumentException: URI is not hierarchical u ImageLoader.getImage.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.--release 8.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.
| cmd | PowerShell (puno ime) | kratko (alias) | napomena |
|---|---|---|---|
set X=C:\a | $X = 'C:\a' | promenljiva počinje sa $ | |
%X% | $X | u stringu: "$X\fajl" (dvostruki navodnici) | |
set PATH=... / %PATH% | $env:PATH = ... / $env:PATH | promenljive okruženja su $env:IME | |
dir | Get-ChildItem | dir, ls, gci | -Recurse = dir /s |
type fajl | Get-Content fajl | type, cat | -Tail 40 = poslednjih 40 linija |
findstr "tekst" fajl | Select-String -Path fajl -Pattern 'tekst' | sls | -Pattern je regex |
copy a b | Copy-Item a b | copy, cp | -Force = prepiši |
move a b | Move-Item a b | move, mv | |
ren a b | Rename-Item a b | ren | |
mkdir a\b\c | New-Item -ItemType Directory -Force a\b\c | mkdir | -Force: ne buni se ako postoji |
del fajl | Remove-Item fajl | del, rm | |
pushd x / popd | Push-Location x / Pop-Location | pushd / popd | isto kao u cmd-u |
start "" prog.exe | Start-Process prog.exe | start | -PassThru vraća proces (PID) |
taskkill /PID 123 | Stop-Process -Id 123 | kill | |
echo %ERRORLEVEL% | $LASTEXITCODE | izlazni kod poslednjeg programa | |
prog > log.txt 2>&1 | isto | isto kao u cmd-u | |
^ na kraju reda | ` `` (backtick) na kraju reda | nastavak komande u sledećem redu |
& "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 promenljiveNavodnici: jednostruki su doslovni, dvostruki umeću promenljive. Za regex i putanje sa $ koristi jednostruke.
# komentarKomentar je #, u cmd-u je to rem.
| Alat | Deo čega | Šta radi |
|---|---|---|
java.exe | JDK/JRE | pokreće program |
javac.exe | JDK | kompajlira .java u .class |
javap.exe | JDK | disasembler: .class → čitljive JVM instrukcije |
jar.exe | JDK | pravi/čita/menja .jar (ZIP) |
cfr.jar | CFR 0.152 | dekompajler: .class → Java izvorni kod (približan) |
cortiveo-agent.jar | Cortiveo | agent: stablo prozora, System.exit, JDBC log, izuzeci |
cortiveo-tracer.jar | Cortiveo | JDWP 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& "$T\jdk17\bin\jar.exe" tf $JAR | Select-String 'ImageLoader|MANIFEST'jar tf = table of contents, file: ispiše sadržaj JAR-a;| Select-String = kao | findstr, zadrži samo linije koje odgovaraju.Ime klase = putanja u ZIP-u: engine.system.ImageLoader → engine/system/ImageLoader.class.
U META-INF/MANIFEST.MF su bitne dve linije:
Main-Class: app.Main: šta pokreće java -jar;Class-Path: .: gde se još traže klase i resursi (. = folder u kome je JAR).javap& "$T\jdk17\bin\javap.exe" -cp $JAR -c -l -p engine.system.ImageLoader-cp $JAR: gde da nađe klasu;-c: prikaži kod (instrukcije);-l: prikaži brojeve linija (LineNumberTable);-p: prikaži i private članove.| Instrukcija | Značenje |
|---|---|
ldc "tekst" | konstanta: stringovi su najbolji tragovi (SQL, imena fajlova, poruke) |
invokevirtual, invokestatic | poziv metode |
new X + invokespecial X."<init>" | new X(...) |
getstatic / putstatic | čitanje / pisanje statičkog polja |
pop | rezultat se baca (često: greška se ignoriše) |
if…, goto | grananje |
areturn / return | povratak iz metode |
Exception table | try/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.
& "$T\jdk17\bin\java.exe" -jar "$T\cfr.jar" $JAR `
--jarfilter '^engine\.system\.ImageLoader$' --outputdir "$W\decompiled"java -jar cfr.jar: pokreće dekompajler;--jarfilter '<regex>': samo klase čije ime odgovara (^…$ = tačno ovo ime; \. = prava tačka);--outputdir: izlaz; pravi foldere po paketu → decompiled\engine\system\ImageLoader.java; ` na kraju prvog reda = nastavak komande (kao ^` u cmd-u).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.
java.exe, a ne javaw.exe, i sve u fajl: java -jar app.jar > run.log 2>&1.hs_err_pid*.log u radnom folderu? Ako ima, pala je sama JVM (native). Gledaj # Problematic frame: i Java frames:.pushd u folder programa), jer program konfiguraciju čita iz tekućeg foldera.diag-PID.log: postoji li System.exit stack, JDBC greška, neuhvaćen izuzetak?javap -c -l na klasi i liniji koju je tracer pokazao → razumeš zašto.-javaagentjava "-javaagent:C:\...\cortiveo-agent.jar=out=C:\...\out,delay=5000" -jar app.jarAgent 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:
diag-PID.log: neuhvaćeni izuzeci, ko je pozvao System.exit (stack sa linijama), JDBC log (URL, drajver, svaka SQLException, i progutana);snapshot-PID-N.json: stablo prozora. Čita se sa cortiveo.exe inspect <fajl> --app-only: klasa, lanac nasleđivanja, interfejsi, listeneri, stavke menija i njihov actionCommand.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 menijaU 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.
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.jarserver=y: program čeka debugger (ne povezuje se on na njega);suspend=y: stoji pre main() dok se debugger ne zakači;address=127.0.0.1:5005: samo lokalno, port 5005.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):
-agentlib:jdwp=…;localhost, port 5005;-jar protiv -cp| Pokretanje | Gde JVM traži klase i resurse |
|---|---|
java -jar app.jar | u app.jar, pa u Class-Path iz manifesta. -cp se ignoriše! |
java -cp "A;B;C" app.Main | redom 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".
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.
(komanda iz §5)
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).
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\"New-Item -ItemType Directory -Force = mkdir koji pravi i sve međufoldere i ne buni se ako postoje;| Out-Null = baci izlaz (da ne ispisuje info o folderu), isto kao >nul u cmd-u;engine\system\ mora da prati paket engine.system.Izmena: vrati Image odmah; u catch bloku ispiši url kakav jeste, bez new File(...).
& "$T\jdk17\bin\javac.exe" --release 8 -encoding UTF-8 `
-cp $JAR `
-d "$W\patch" `
"$W\patch-src\engine\system\ImageLoader.java"| Deo | Zašto |
|---|---|
--release 8 | bytecode i API se proveravaju prema Javi 8 (vidi §12) |
-encoding UTF-8 | naša slova u izvoru ostaju ispravna |
-cp $JAR | kompajler 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.
patch prvim i dokaži koja klasa je učitanaPush-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 'Push-Location $APP = pushd: program čita konfiguraciju iz tekućeg foldera;-verbose:class = JVM ispiše svaku učitanu klasu i odakle;"$W\patch;app.jar;.": patch prvi; . na kraju zamenjuje Class-Path: . iz manifesta;> … 2>&1: hiljade linija idu u fajl;Select-String traži samo našu klasu (\. = prava tačka, razmak na kraju = tačno to ime).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.
Isključi zaobilaznicu iz §9 (Rename-Item resources resources.off) i pokreni ponovo. Ako program radi, popravka je stvarna.
META-INF\*.SF, *.RSA) ili sealed paket → SecurityException.-cp, jer kod -jar se -cp ignoriše.jar uf app.jar -C patch engine/system/ImageLoader.class menja klasu u JAR-u. Pre toga obavezno napravi kopiju JAR-a.ClassFileTransformer-om menja bytecode pri učitavanju, bez diranja JAR-a i classpath-a.& "$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"list: pokrenuti Java procesi (PID + glavna klasa);agentmain umesto premain.Dve lekcije iz prakse:
AgentLoadException: 0, iako je agent uspešno učitan (Java 8 vraća "0" = uspeh). Cortiveo Attach to već ignoriše.| Kako | Rezultat na Javi 8 |
|---|---|
-source 8 -target 8 | kompajlira se bez greške, ali puca u radu: NoSuchMethodError (npr. ByteBuffer.flip()) |
--release 8 | ispravno: 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.
# 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