Η εφαρμογή λειτουργούσε στο Android 9 και πεθαίνει στο ίδιο τηλέφωνο μετά την ενημέρωση σε Android 10. Το προφίλ σας είναι ανέπαφο, τα πιστοποιητικά είναι τα ίδια και η κατάρρευση έρχεται πριν από οποιαδήποτε κίνηση ταυτοποίησης. Το logcat δείχνει πρώτα έναν μηδενικό δείκτη, και λίγες γραμμές πιο πάνω βρίσκεται Cannot run program ...: error=13, Permission denied. Το μήνυμα αφορά την εκτέλεση ενός αρχείου, όχι την ανάγνωση της διαμόρφωσής σας.

Τι αρνείται πραγματικά το σφάλμα 13

Το Android 10, επίπεδο API 29, σταμάτησε τις εφαρμογές από το να εκτελούν αρχεία που έγραφαν στον δικό τους κατάλογο δεδομένων. Ο κανόνας στοχεύει χώρο που πρώτα είναι εγγράψιμος και μετά εκτελέσιμος, δηλαδή ακριβώς αυτό που δημιουργεί ένας εκκινητής που αντιγράφει από τα assets. Παλαιότερες εκδόσεις του πυρήνα ics-openvpn αποσυμπιέζουν ένα εκτελέσιμο αρχείο openvpn από το APK στην πρώτη εκτέλεση και το ξεκινούν με ProcessBuilder. Η αποσυμπίεση εξακολουθεί να πετυχαίνει, αυτό που απορρίπτεται είναι η εκτέλεση. Μια εφαρμογή που στοχεύει το API 29 κληρονομεί τον κανόνα ακόμη και όταν τρέχει σε συσκευή που κυκλοφόρησε με παλαιότερη έκδοση, γι’ αυτό μια ενημέρωση μπορεί να σπάσει μια έκδοση που δεν άγγιξε κανείς.

Τίποτα στην άρνηση δεν είναι ειδικό για ένα όνομα φακέλου. Ένα αρχείο που δημιούργησε η εφαρμογή κάτω από τον κατάλογο δεδομένων της αντιμετωπίζεται ως μη εκτελέσιμο ανεξάρτητα από τη διαδρομή, οπότε getFilesDir(), getCacheDir() και ένας προσαρμοσμένος υποκατάλογος αποτυγχάνουν όλα με τον ίδιο τρόπο.

Διαχωρίστε την κατάρρευση του βοηθητικού από ένα πρόβλημα προφίλ

Διαβάστε το αρχείο καταγραφής γύρω από την εκκίνηση της διεργασίας αντί να κάνετε κύλιση από την αρχή. Η υπογραφή που ψάχνετε είναι η γραμμή error=13, και το ίχνος στοίβας πιο πάνω συνήθως ονομάζει τη μέθοδο του εκκινητή που κάλεσε το ProcessBuilder:

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

Αν το βοηθητικό δεν ξεκίνησε ποτέ, δεν θα δείτε καθόλου έξοδο καταγραφής από το OpenVPN: ούτε διαπραγμάτευση κρυπτογράφησης, ούτε προωθημένες διαδρομές, ούτε διαμόρφωση DNS. Αν όμως αυτές οι γραμμές εμφανιστούν και η αποτυχία έρθει αργότερα, το δυαδικό έτρεξε και το πρόβλημα είναι στο προφίλ, στον διακομιστή ή στο επίπεδο δρομολόγησης. Αυτή η μία διάκριση εξοικονομεί πολύ χρόνο που θα πήγαινε σε επεξεργασία αρχείων .ovpn που ποτέ δεν έφταιγαν. Σας λέει επίσης ποια από τις δύο διορθώσεις παρακάτω χρειάζεστε πραγματικά.

Ελέγξτε επίσης το επίπεδο API στη συσκευή που δοκιμάζετε, όχι αυτό στη διαμόρφωση της έκδοσής σας. Μια εικόνα εξομοιωτή στο API 28 δεν αναπαράγει τίποτα, γιατί ο περιορισμός δαγκώνει μόνο από το 29 και πάνω. Ελέγξτε ταυτόχρονα το SDK-στόχο στο αρχείο έκδοσης, η αύξησή του είναι αυτό που φέρνει τον κανόνα.

adb shell getprop ro.build.version.sdk

Γιατί η αντιγραφή του δυαδικού αλλού δεν βοηθά

Η προφανής λύση, να γράψετε το βοηθητικό σε άλλον εγγράψιμο κατάλογο, αποτυγχάνει για τον ίδιο λόγο που απέτυχε το αρχικό. Το Android εξακολουθεί να επιτρέπει την εκτέλεση κοινόχρηστων βιβλιοθηκών που εγκαταστάθηκαν στον κατάλογο εγγενών βιβλιοθηκών της εφαρμογής, γιατί τις πακετάρει ο εγκαταστάτης αντί να γράφονται κατά την εκτέλεση. Αυτός ο κατάλογος είναι το μόνο μέρος στο οποίο πρέπει να δείχνει ο εκκινητής. Ό,τι αποσυμπιέζετε μόνοι σας κατά την εκτέλεση, όπως κι αν το ονομάσετε, μένει στην κατηγορία που απορρίπτεται.

Η εξωτερική αποθήκευση είναι χειρότερη: είναι προσαρτημένη με noexec, και ένα βοηθητικό που βρίσκεται εκεί δεν μπορεί να ξεκινήσει από καμία εφαρμογή. Ο καθαρισμός των δεδομένων της εφαρμογής επίσης δεν επαναφέρει την παλιά συμπεριφορά, αφού η επόμενη εκκίνηση απλώς ξαναδημιουργεί το ίδιο μη εκτελέσιμο αντίγραφο.

Στρέψτε τον εκκινητή σε μια πακεταρισμένη βιβλιοθήκη

Μετονομάστε το βοηθητικό σε όνομα κοινόχρηστης βιβλιοθήκης και διανείμετέ το κάτω από jniLibs για κάθε ABI που υποστηρίζετε, και μετά κάντε τον εκκινητή να επιστρέφει τη διαδρομή της εγκατεστημένης βιβλιοθήκης από το API 28 και μετά:

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

Η βιβλιοθήκη πρέπει να αποσυμπιεστεί κατά την εγκατάσταση ώστε αυτή η διαδρομή να περιέχει πραγματικό αρχείο, οπότε ορίστε το android:extractNativeLibs="true" στο στοιχείο <application>. Αν ένας συγχωνευτής manifest φέρει διαφορετική τιμή, προσθέστε το tools:replace="android:extractNativeLibs" δίπλα του. Η αναφορά του στοιχείου application τεκμηριώνει τι ελέγχει το χαρακτηριστικό.

Προσέξτε για διπλή τοπική μεταβλητή όταν διορθώνετε τον εκκινητή με το χέρι. Παλαιότερες πηγές δηλώνουν το nativeAPI αργότερα στην ίδια μέθοδο, και μια δεύτερη δήλωση είναι σφάλμα μεταγλώττισης και όχι χρόνου εκτέλεσης, οπότε η έκδοση αποτυγχάνει γρήγορα και το λάθος φαίνεται εύκολα. Κρατήστε τη λίστα ABI στο μπλοκ Gradle συγχρονισμένη με τους φακέλους που προσθέτετε.

Κάντε το τελικό προϊόν να ταιριάζει με την έκδοση δοκιμής

Ένα APK αποσφαλμάτωσης μπορεί να σηκώσει το τούνελ ενώ ένα bundle τελικής έκδοσης εξακολουθεί να καταρρέει, γιατί ένα bundle πακετάρει τις εγγενείς βιβλιοθήκες σε διαφορετική μορφή και μπορεί να τις σερβίρει συμπιεσμένες από το αρχείο. Αν αποτυγχάνουν μόνο οι εκδόσεις από bundle, προσθέστε αυτό στο gradle.properties και ξαναχτίστε:

android.bundle.enableUncompressedNativeLibs=false

Έπειτα επιβεβαιώστε ότι το εγκατεστημένο πακέτο αναφέρει το ABI που περιμένετε, και επαναλάβετε τον έλεγχο σε συσκευή 32-bit και 64-bit αν διανέμετε και τις δύο. Ο διαχειριστής πακέτων ονομάζει το ABI που επέλεξε, οπότε μια συσκευή που έπεσε σε 32 bit εμφανίζεται εδώ αντί σε αναφορά κατάρρευσης:

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

Μια βιβλιοθήκη που λείπει για ένα ABI αναπαράγει το αρχικό σφάλμα άδειας μόνο σε εκείνη τη συσκευή, γι’ αυτό ένα επιτυχημένο τηλέφωνο δοκιμής δεν είναι απόδειξη. Διανείμετε τη σημαία του manifest, το όνομα της βιβλιοθήκης και τη ρύθμιση του bundle μαζί, αλλάζοντας το ένα και ξεχνώντας τα άλλα επιστρέφετε στην ίδια κατάρρευση. Η διαρκής λύση είναι να περάσετε σε συντηρούμενη έκδοση πυρήνα αντί να κουβαλάτε το patch για πάντα.

Σχετικά άρθρα