@@ -2,8 +2,10 @@ package versionaware
22
33import (
44 "context"
5+ "debug/elf"
56 "fmt"
67 "sort"
8+ "strings"
79
810 "github.com/facebookincubator/go-belt/tool/logger"
911 "github.com/xaionaro-go/aidl/binder"
@@ -60,6 +62,12 @@ func NewTransport(
6062 return nil , fmt .Errorf ("versionaware: API level %d is not supported; supported API levels: %v" , targetAPI , supportedAPILevels ())
6163 }
6264
65+ // Narrow revision candidates by checking which methods exist in
66+ // libbinder.so's BpServiceManager. This distinguishes old-style
67+ // (no getService2/checkService2) from new-style method ordering
68+ // without any binder transactions.
69+ revisions = filterRevisionsBySOMethodSet (revisions )
70+
6371 var version string
6472 switch len (revisions ) {
6573 case 1 :
@@ -114,11 +122,164 @@ func supportedAPILevels() []int {
114122}
115123
116124const (
117- serviceManagerHandle = uint32 (0 )
118- serviceManagerDescriptor = "android.os.IServiceManager"
125+ serviceManagerHandle = uint32 (0 )
126+ serviceManagerDescriptor = "android.os.IServiceManager"
119127 activityManagerDescriptor = "android.app.IActivityManager"
120128)
121129
130+ // filterRevisionsBySOMethodSet reads BpServiceManager symbols from
131+ // /system/lib64/libbinder.so to determine which methods exist on
132+ // the device, then filters revision candidates to those whose method
133+ // set matches.
134+ func filterRevisionsBySOMethodSet (revisions []string ) []string {
135+ deviceMethods := readBpServiceManagerMethods ()
136+ if len (deviceMethods ) == 0 {
137+ return revisions // can't read .so, don't filter
138+ }
139+
140+ var filtered []string
141+ for _ , rev := range revisions {
142+ table , ok := Tables [rev ]
143+ if ! ok {
144+ continue
145+ }
146+ smMethods := table [serviceManagerDescriptor ]
147+ if smMethods == nil {
148+ continue
149+ }
150+ if methodSetMatches (smMethods , deviceMethods ) {
151+ filtered = append (filtered , rev )
152+ }
153+ }
154+
155+ if len (filtered ) == 0 {
156+ return revisions // no match found, don't filter
157+ }
158+ return filtered
159+ }
160+
161+ // methodSetMatches returns true if the version table's method set for
162+ // an interface matches the methods found in the device's .so.
163+ // A match means: every method in the table exists in the device methods,
164+ // and no device methods are missing from the table.
165+ func methodSetMatches (
166+ tableMethods map [string ]binder.TransactionCode ,
167+ deviceMethods map [string ]bool ,
168+ ) bool {
169+ // Check that every method in the table exists on the device.
170+ for method := range tableMethods {
171+ if ! deviceMethods [method ] {
172+ return false
173+ }
174+ }
175+ // Check that every device method exists in the table.
176+ for method := range deviceMethods {
177+ if _ , ok := tableMethods [method ]; ! ok {
178+ return false
179+ }
180+ }
181+ return true
182+ }
183+
184+ // readBpServiceManagerMethods reads libbinder.so and extracts the
185+ // method names from BpServiceManager symbols.
186+ func readBpServiceManagerMethods () map [string ]bool {
187+ paths := []string {
188+ "/system/lib64/libbinder.so" ,
189+ "/system/lib/libbinder.so" ,
190+ }
191+
192+ for _ , path := range paths {
193+ methods := parseBpMethods (path , "BpServiceManager" )
194+ if len (methods ) > 0 {
195+ return methods
196+ }
197+ }
198+ return nil
199+ }
200+
201+ // parseBpMethods reads an ELF shared library and extracts method names
202+ // from the BpXxx class's exported symbols. Returns a set of method names.
203+ func parseBpMethods (
204+ path string ,
205+ className string ,
206+ ) map [string ]bool {
207+ f , err := elf .Open (path )
208+ if err != nil {
209+ return nil
210+ }
211+ defer f .Close ()
212+
213+ symbols , err := f .DynamicSymbols ()
214+ if err != nil {
215+ return nil
216+ }
217+
218+ // Match demangled C++ symbols like:
219+ // _ZN7android2os16BpServiceManager10getServiceE...
220+ // The method name is after the class name length+name prefix.
221+ prefix := className
222+ methods := map [string ]bool {}
223+
224+ for _ , sym := range symbols {
225+ if sym .Info & 0xf != uint8 (elf .STT_FUNC ) {
226+ continue
227+ }
228+ name := sym .Name
229+ // Look for mangled name containing the class name.
230+ idx := findMangledMethod (name , prefix )
231+ if idx == "" {
232+ continue
233+ }
234+ methods [idx ] = true
235+ }
236+
237+ return methods
238+ }
239+
240+ // findMangledMethod extracts the method name from a C++ mangled symbol
241+ // that belongs to the given class. Returns "" if not a match.
242+ // Handles Itanium name mangling: _ZN<len><namespace><len><class><len><method>E...
243+ func findMangledMethod (
244+ mangled string ,
245+ className string ,
246+ ) string {
247+ // Quick filter: must contain the class name.
248+ classLen := fmt .Sprintf ("%d%s" , len (className ), className )
249+ idx := strings .Index (mangled , classLen )
250+ if idx < 0 {
251+ return ""
252+ }
253+
254+ // Skip constructors/destructors (C1, C2, D0, D1, D2).
255+ rest := mangled [idx + len (classLen ):]
256+ if len (rest ) < 2 {
257+ return ""
258+ }
259+ if rest [0 ] == 'C' || rest [0 ] == 'D' {
260+ return ""
261+ }
262+
263+ // Parse the method name length + name.
264+ nameLen := 0
265+ i := 0
266+ for i < len (rest ) && rest [i ] >= '0' && rest [i ] <= '9' {
267+ nameLen = nameLen * 10 + int (rest [i ]- '0' )
268+ i ++
269+ }
270+ if nameLen == 0 || i + nameLen > len (rest ) {
271+ return ""
272+ }
273+
274+ methodName := rest [i : i + nameLen ]
275+ // Convert to camelCase (first letter lowercase) to match AIDL method names.
276+ if len (methodName ) > 0 && methodName [0 ] >= 'A' && methodName [0 ] <= 'Z' {
277+ methodName = string (methodName [0 ]+ 32 ) + methodName [1 :]
278+ }
279+
280+ return methodName
281+ }
282+
122283// probeRevision determines which revision of the given API level matches
123284// the running device by calling a distinguishing method on IActivityManager.
124285//
0 commit comments