001    /**
002     * 
003     */
004    package org.bridj;
005    
006    import java.io.*;
007    import java.util.regex.*;
008    import java.lang.annotation.Annotation;
009    import java.lang.reflect.AnnotatedElement;
010    import java.lang.reflect.Constructor;
011    import java.lang.reflect.Member;
012    import java.lang.reflect.Method;
013    import java.lang.reflect.Modifier;
014    import java.util.ArrayList;
015    import java.util.Collections;
016    import java.util.HashMap;
017    import java.util.List;
018    import java.util.Map;
019    import java.util.Set;
020    import java.util.Arrays;
021    
022    import org.bridj.demangling.Demangler.DemanglingException;
023    import org.bridj.demangling.Demangler.MemberRef;
024    import org.bridj.demangling.Demangler.Symbol;
025    import org.bridj.ann.Virtual;
026    import org.bridj.demangling.GCC4Demangler;
027    import org.bridj.demangling.VC9Demangler;
028    import java.lang.reflect.Type;
029    import static org.bridj.Pointer.*;
030    import static org.bridj.util.AnnotationUtils.*;
031    
032    import java.util.Collection;
033    import org.bridj.Platform.DeleteFiles;
034    import org.bridj.demangling.Demangler;
035    import org.bridj.util.ProcessUtils;
036    import org.bridj.util.StringUtils;
037    
038    /**
039     * Representation of a native shared library, with symbols retrieval / matching facilities.<br>
040     * This class is not meant to be used by end users, it's used by pluggable runtimes instead.
041     * @author ochafik
042     */
043    public class NativeLibrary {
044            volatile long handle, symbols;
045            String path;
046        final File canonicalFile;
047            //Map<Class<?>, long[]> callbacks = new HashMap<Class<?>, long[]>();
048            NativeEntities nativeEntities = new NativeEntities();
049            
050            Map<Long, Symbol> addrToName;
051            Map<String, Symbol> nameToSym;
052    //      Map<String, Long> nameToAddr;
053            
054        
055            protected NativeLibrary(String path, long handle, long symbols) throws IOException {
056                    this.path = path;
057                    this.handle = handle;
058                    this.symbols = symbols;
059            this.canonicalFile = path == null ? null : new File(path).getCanonicalFile();
060            
061            Platform.addNativeLibrary(this);
062            }
063            
064            long getSymbolsHandle() {
065                    return symbols;
066            }
067            NativeEntities getNativeEntities() {
068                    return nativeEntities;
069            }
070            static String followGNULDScript(String path) {
071                    try {
072                            Reader r = new FileReader(path);
073                            try {
074                                    char c;
075                                    while ((c = (char)r.read()) == ' ' || c == '\t' || c == '\n') {}
076                                    if (c == '/' && r.read() == '*') {
077                                            BufferedReader br = new BufferedReader(r);
078                                            r = br;
079                                            String line;
080                                            StringBuilder b = new StringBuilder("/*");
081                                            while ((line = br.readLine()) != null)
082                                                    b.append(line).append('\n');
083                                            String src = b.toString();
084                                            Pattern ldGroupPattern = Pattern.compile("GROUP\\s*\\(\\s*([^\\s)]+)[^)]*\\)");
085                                            Matcher m = ldGroupPattern.matcher(src);
086                                            if (m.find()) {
087                                                    String actualPath = m.group(1);
088                                                    if (BridJ.verbose)
089                                BridJ.info("Parsed LD script '" + path + "', found absolute reference to '" + actualPath + "'");
090                                                    return actualPath;
091                                            } else {
092                                                    BridJ.error("Failed to parse LD script '" + path + "' !");
093                                            }
094                                    }
095                            } finally {
096                                    r.close();
097                            }
098                    } catch (Throwable th) {
099                            BridJ.error("Unexpected error: " + th, th);
100                    }
101                    return path;
102            }
103            public static NativeLibrary load(String path) throws IOException {
104                    long handle = 0;
105                    File file = new File(path);
106                    boolean exists = file.exists();
107                    if (file.isAbsolute() && !exists)
108                            return null;
109                    
110                    if (Platform.isUnix() && exists)
111                            path = followGNULDScript(path);
112                    
113                    handle = JNI.loadLibrary(path);
114                    if (handle == 0)
115                            return null;
116                    long symbols = JNI.loadLibrarySymbols(path);
117                    return new NativeLibrary(path, handle, symbols);
118            }
119            
120            /*public boolean methodMatchesSymbol(Class<?> declaringClass, Method method, String symbol) {
121                    return symbol.contains(method.getName()) && symbol.contains(declaringClass.getSimpleName());
122            }*/
123            
124            long getHandle() {
125                    if (path != null && handle == 0)
126                            throw new RuntimeException("Library was released and cannot be used anymore");
127                    return handle;
128            }
129            @Override
130            protected void finalize() throws Throwable {
131                    release();
132            }
133            public synchronized void release() {
134                    if (handle == 0)
135                            return;
136                    
137                    if (BridJ.verbose)
138                            BridJ.info("Releasing library '" + path + "'");
139                    
140                    nativeEntities.release();
141                    
142                    JNI.freeLibrarySymbols(symbols);
143                    JNI.freeLibrary(handle);
144                    handle = 0;
145            
146            if (canonicalFile != null && Platform.temporaryExtractedLibraryCanonicalFiles.remove(canonicalFile)) {
147                if (canonicalFile.delete()) {
148                    if (BridJ.verbose)
149                        BridJ.info("Deleted temporary library file '" + canonicalFile + "'");
150                } else
151                    BridJ.error("Failed to delete temporary library file '" + canonicalFile + "'");
152            }
153                
154            }
155        public Pointer<?> getSymbolPointer(String name) {
156            return pointerToAddress(getSymbolAddress(name));
157        }
158            public long getSymbolAddress(String name) {
159                    if (nameToSym != null) {
160                            Symbol addr = nameToSym.get(name);
161    //                      long addr = nameToAddr.get(name);
162    //                      if (addr != 0)
163                            if (addr != null)
164                                    return addr.getAddress();
165                    }
166                    long address = JNI.findSymbolInLibrary(getHandle(), name);
167                    if (address == 0)
168                            address = JNI.findSymbolInLibrary(getHandle(), "_" + name);
169                    return address;
170            }
171    
172        public synchronized Symbol getSymbol(AnnotatedElement member) throws FileNotFoundException {
173            org.bridj.ann.Symbol mg = getAnnotation(org.bridj.ann.Symbol.class, member);
174            String name = null;
175            if (member instanceof Member)
176                name = ((Member)member).getName();
177            
178            List<String> names = new ArrayList<String>();
179            if (mg != null)
180                            names.addAll(Arrays.asList(mg.value()));
181                    if (name != null)
182                            names.add(name);
183                    
184                    for (String n : names)
185                    {
186                            Symbol handle = getSymbol(n);
187                            if (handle == null)
188                                    handle = getSymbol("_" + n);
189                            if (handle == null)
190                                    handle = getSymbol(n + (Platform.useUnicodeVersionOfWindowsAPIs ? "W" : "A"));
191                            if (handle != null)
192                                    return handle;
193                    }
194                    
195            if (member instanceof Method) {
196                Method method = (Method)member;
197                for (Demangler.Symbol symbol : getSymbols()) {
198                    if (symbol.matches(method))
199                        return symbol;
200                }
201            }
202            return null;
203        }
204            
205            public boolean isMSVC() {
206                    return Platform.isWindows();
207            }
208        /** Filter for symbols */
209            public interface SymbolAccepter {
210            boolean accept(Symbol symbol);
211        }
212        public Symbol getFirstMatchingSymbol(SymbolAccepter accepter) {
213            for (Symbol symbol : getSymbols())
214                if (accepter.accept(symbol))
215                    return symbol;
216            return null;
217        }
218            public Collection<Demangler.Symbol> getSymbols() {
219            try {
220                scanSymbols();
221            } catch (Exception ex) {
222                assert BridJ.error("Failed to scan symbols of library '" + path + "'", ex);
223            }
224                    return nameToSym == null ? Collections.EMPTY_LIST : Collections.unmodifiableCollection(nameToSym.values());
225            }
226            public String getSymbolName(long address) {
227                    if (addrToName == null && getSymbolsHandle() != 0)//Platform.isUnix())
228                            return JNI.findSymbolName(getHandle(), getSymbolsHandle(), address);
229            
230                    Demangler.Symbol symbol = getSymbol(address);
231                    return symbol == null ? null : symbol.getSymbol();
232            }
233            
234            public Symbol getSymbol(long address) {
235                    try {
236                            scanSymbols();
237                            Symbol symbol = addrToName.get(address);
238                            return symbol;
239                    } catch (Exception ex) {
240                            throw new RuntimeException("Failed to get name of address " + address, ex);
241                    }
242            }
243            public Symbol getSymbol(String name) {
244                    try {
245                            Symbol symbol;
246                            long addr;
247                            
248                            if (nameToSym == null) {// symbols not scanned yet, try without them !
249                                    addr = JNI.findSymbolInLibrary(getHandle(), name);
250                                    if (addr != 0) {
251                                            symbol = new Symbol(name, this);
252                                            symbol.setAddress(addr);
253                                            return symbol;          
254                                    }
255                            }
256                scanSymbols();
257                if (nameToSym == null)
258                    return null;
259                
260                            symbol = nameToSym.get(name);
261                            if (addrToName == null) {
262                                    if (symbol == null) {
263                                            addr = JNI.findSymbolInLibrary(getHandle(), name);
264                                            if (addr != 0) {
265                                                    symbol = new Symbol(name, this);
266                                                    symbol.setAddress(addr);
267                                                    nameToSym.put(name, symbol);
268                                            }
269                                    }
270                            }
271                            return symbol;
272                    } catch (Exception ex) {
273                            ex.printStackTrace();
274                            return null;
275    //                      throw new RuntimeException("Failed to get symbol " + name, ex);
276                    }
277            }
278            void scanSymbols() throws Exception {
279                    if (addrToName != null)
280                            return;
281                    
282                    nameToSym = new HashMap<String, Symbol>();
283    //              nameToAddr = new HashMap<String, Long>();
284                    
285                    String[] symbs = null;
286                    if (false) // TODO turn to false !!!
287                    try {
288                            if (Platform.isMacOSX()) {
289                                    Process process = java.lang.Runtime.getRuntime().exec(new String[] {"nm", "-gj", path});
290                                    BufferedReader rin = new BufferedReader(new InputStreamReader(process.getInputStream()));
291                                    String line;
292                                    List<String> symbsList = new ArrayList<String>();
293                                    while ((line = rin.readLine()) != null) {
294                                            symbsList.add(line);
295                                    }
296                                    symbs = symbsList.toArray(new String[symbsList.size()]);
297                            }
298                    } catch (Exception ex) {
299                            ex.printStackTrace();
300                    }
301                    if (symbs == null) {
302                            //System.out.println("Calling getLibrarySymbols");
303                            symbs = JNI.getLibrarySymbols(getHandle(), getSymbolsHandle());
304                            //System.out.println("Got " + symbs + " (" + (symbs == null ? "null" : symbs.length + "") + ")");
305                    }
306                    
307                    if (symbs == null)
308                            return;
309                    
310                    addrToName = new HashMap<Long, Demangler.Symbol>();
311                    
312                    boolean is32 = !Platform.is64Bits();
313                    for (String name : symbs) {
314                            if (name == null)
315                                    continue;
316                                    
317                            long addr = JNI.findSymbolInLibrary(getHandle(), name);
318                            if (addr == 0 && name.startsWith("_")) {
319                                    String n2 = name.substring(1);
320                                    addr = JNI.findSymbolInLibrary(getHandle(), n2);
321                    if (addr == 0) {
322                        n2 = "_" + name;
323                        addr = JNI.findSymbolInLibrary(getHandle(), n2);
324                    }
325                    if (addr != 0)
326                        name = n2;
327    
328                            }
329                            if (addr == 0) {
330                                    if (BridJ.verbose)
331                                            BridJ.warning("Symbol '" + name + "' not found.");
332                                    continue;
333                            }
334                            //if (is32)
335                            //      addr = addr & 0xffffffffL;
336                            //System.out.println("Symbol " + Long.toHexString(addr) + " = '" + name + "'");
337                            
338                            Symbol sym = new Demangler.Symbol(name, this);
339                            sym.setAddress(addr);
340                            addrToName.put(addr, sym);
341                            nameToSym.put(name, sym);
342                            //nameToAddr.put(name, addr);
343                            //System.out.println("'" + name + "' = \t" + TestCPP.hex(addr) + "\n\t" + sym.getParsedRef());
344                    }
345                    if (BridJ.debug) {
346                            BridJ.info("Found " + nameToSym.size() + " symbols in '" + path + "' :");
347                            
348                            for (Symbol sym : nameToSym.values())
349                                    BridJ.info("DEBUG(BridJ): library=\"" + path + "\", symbol=\"" + sym.getSymbol() + "\", address=" + Long.toHexString(sym.getAddress()) + ", demangled=\"" + sym.getParsedRef() + "\""); 
350                            
351                            //for (Symbol sym : nameToSym.values())
352                            //      System.out.println("Symbol '" + sym + "' = " + sym.getParsedRef());
353                    }
354            }
355    
356            public MemberRef parseSymbol(String symbol) throws DemanglingException {
357            if ("__cxa_pure_virtual".equals(symbol))
358                return null;
359            
360                    Demangler demangler;
361                    if (Platform.isWindows())
362                            demangler = new VC9Demangler(this, symbol);
363                    else
364                            demangler = new GCC4Demangler(this, symbol);
365                    return demangler.parseSymbol();
366            }
367    }