Aplikacija je delovala v Androidu 9 in umre na istem telefonu po posodobitvi na Android 10. Vaš profil je nedotaknjen, potrdila so enaka, sesutje pa pride pred vsakršnim prometom preverjanja pristnosti. Logcat najprej pokaže ničelni kazalec, nekaj vrstic nad njim pa stoji Cannot run program ...: error=13, Permission denied. To sporočilo govori o izvajanju datoteke, ne o branju vaše konfiguracije.

Čemu napaka 13 v resnici odreka

Android 10, raven API 29, je aplikacijam preprečil izvajanje datotek, ki so jih zapisale v svojo lastno podatkovno mapo. Pravilo cilja na shrambo, ki je najprej zapisljiva in nato izvedljiva, in prav to ustvari zaganjalnik, ki kopira iz assets. Starejše gradnje jedra ics-openvpn pri prvem zagonu razpakirajo iz APK-ja izvedljivo datoteko openvpn in jo zaženejo z ProcessBuilder. Razpakiranje še vedno uspe; zavrnjeno je prav izvajanje. Aplikacija, ki cilja API 29, pravilo podeduje tudi kadar teče na napravi, ki je prišla s starejšo različico, zato lahko posodobitev pokvari gradnjo, ki se je nihče ni dotaknil.

Nič pri zavrnitvi ni značilno za eno ime mape. Datoteka, ki jo je aplikacija ustvarila v svoji podatkovni mapi, se obravnava kot neizvedljiva ne glede na pot, zato getFilesDir(), getCacheDir() in lastna podmapa vsi spodletijo enako.

Ločite sesutje pomočnika od težave s profilom

Berite dnevnik okoli zagona procesa, namesto da drsite od vrha. Iskani podpis je vrstica error=13, sled sklada nad njo pa običajno poimenuje metodo zaganjalnika, ki je poklicala ProcessBuilder:

adb logcat -d | grep -n -i -E "cannot run program|permission denied|startOpenVPN"

Če se pomočnik ni nikoli zagnal, ne boste videli nobenega izhoda dnevnika OpenVPN: niti pogajanja o šifrah, niti potisnjenih poti, niti konfiguracije DNS. Če se te vrstice vseeno pojavijo in napaka pride pozneje, je binarna datoteka tekla in težava je v profilu, strežniku ali usmerjevalni plasti. Prav ta razlika prihrani veliko časa, ki bi sicer šel za urejanje datotek .ovpn, ki nikoli niso bile krive. Pove vam tudi, katero od dveh popravkov spodaj v resnici potrebujete.

Preverite tudi raven API na napravi, ki jo testirate, ne tiste v svoji konfiguraciji gradnje. Podoba emulatorja na API 28 ne reproducira ničesar, ker omejitev ugrizne šele od 29 naprej. Hkrati preverite ciljni SDK v svoji datoteki gradnje: prav njegovo zvišanje pritegne to pravilo.

adb shell getprop ro.build.version.sdk

Zakaj kopiranje binarne datoteke drugam ne pomaga

Očitna rešitev, zapis pomočnika v drugo zapisljivo mapo, spodleti iz istega razloga kot izvirnik. Android še vedno dovoljuje izvajanje deljenih knjižnic, nameščenih v mapo izvornih knjižnic aplikacije, ker jih pakira namestitveni program in se ne zapisujejo med izvajanjem. Prav ta mapa je edino mesto, kamor naj bi zaganjalnik kazal. Vse, kar sami razpakirate med izvajanjem, kakor koli to imenujete, ostane v zavrnjeni kategoriji.

Zunanja shramba je slabša: priklopljena je z noexec in pomočnika, ki je tam, ne more zagnati nobena aplikacija. Brisanje podatkov aplikacije tudi ne povrne starega vedenja, ker naslednji zagon preprosto znova ustvari isto neizvedljivo kopijo.

Usmerite zaganjalnik na zapakirano knjižnico

Preimenujte pomočnika v ime deljene knjižnice in ga dostavite pod jniLibs za vsak ABI, ki ga podpirate, nato pa naj zaganjalnik od API 28 naprej vrača pot do nameščene knjižnice:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
    return new File(context.getApplicationInfo().nativeLibraryDir,
                    "libovpnexec.so").getPath();
}

Knjižnica mora biti razpakirana ob namestitvi, da ta pot vsebuje resnično datoteko, zato nastavite android:extractNativeLibs="true" na elementu <application>. Če združevalnik manifestov prinese drugo vrednost, dodajte tools:replace="android:extractNativeLibs" ob njej. referenca elementa application dokumentira, kaj nadzira ta atribut.

Pazite na podvojeno lokalno spremenljivko, ko zaganjalnik popravljate ročno. Starejši viri deklarirajo nativeAPI pozneje v isti metodi, druga deklaracija pa je napaka pri prevajanju, ne pri izvajanju, zato gradnja hitro spodleti in napako je lahko opaziti. Vzdržujte seznam ABI v bloku Gradle usklajen z mapami, ki jih dodajate.

Uskladite izdajni artefakt s testno gradnjo

Debug APK lahko dvigne tunel, medtem ko izdajni bundle še vedno sesuje, ker bundle pakira izvorne knjižnice v drugačni obliki in jih lahko iz arhiva streže stisnjene. Če spodletijo le gradnje iz bundle, dodajte to v gradle.properties in znova zgradite:

android.bundle.enableUncompressedNativeLibs=false

Nato potrdite, da nameščeni paket poroča ABI, ki ga pričakujete, in ponovite preverjanje na 32-bitni in 64-bitni napravi, če dostavljate obe. Upravitelj paketov navede izbrani ABI, zato se naprava, ki je padla na 32 bitov, pojavi tukaj in ne v poročilu o sesutju:

adb shell dumpsys package com.example.openvpn | grep -i primaryCpuAbi

Manjkajoča knjižnica za en ABI ponovi izvirno napako dostopa samo na tisti napravi, zato en uspešen testni telefon ni dokaz. Dostavite zastavico manifesta, ime knjižnice in nastavitev bundle skupaj; če spremenite eno in pozabite druge, se vrnete k istemu sesutju. Trajna rešitev je prehod na vzdrževano izdajo jedra, namesto da bi popravek nosili večno.

Sorodni članki