001/*
002 * BridJ - Dynamic and blazing-fast native interop for Java.
003 * http://bridj.googlecode.com/
004 *
005 * Copyright (c) 2010-2013, Olivier Chafik (http://ochafik.com/)
006 * All rights reserved.
007 *
008 * Redistribution and use in source and binary forms, with or without
009 * modification, are permitted provided that the following conditions are met:
010 * 
011 *     * Redistributions of source code must retain the above copyright
012 *       notice, this list of conditions and the following disclaimer.
013 *     * Redistributions in binary form must reproduce the above copyright
014 *       notice, this list of conditions and the following disclaimer in the
015 *       documentation and/or other materials provided with the distribution.
016 *     * Neither the name of Olivier Chafik nor the
017 *       names of its contributors may be used to endorse or promote products
018 *       derived from this software without specific prior written permission.
019 * 
020 * THIS SOFTWARE IS PROVIDED BY OLIVIER CHAFIK AND CONTRIBUTORS ``AS IS'' AND ANY
021 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
022 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
023 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
024 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
025 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
026 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
027 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
028 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
029 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
030 */
031/**
032 *
033 */
034package org.bridj;
035
036import java.io.*;
037import java.util.regex.*;
038import java.lang.annotation.Annotation;
039import java.lang.reflect.AnnotatedElement;
040import java.lang.reflect.Constructor;
041import java.lang.reflect.Member;
042import java.lang.reflect.Method;
043import java.lang.reflect.Modifier;
044import java.util.ArrayList;
045import java.util.Collections;
046import java.util.HashMap;
047import java.util.List;
048import java.util.Map;
049import java.util.Set;
050import java.util.Arrays;
051
052import org.bridj.demangling.Demangler.DemanglingException;
053import org.bridj.demangling.Demangler.MemberRef;
054import org.bridj.demangling.Demangler.Symbol;
055import org.bridj.ann.Virtual;
056import org.bridj.ann.Name;
057import org.bridj.demangling.GCC4Demangler;
058import org.bridj.demangling.VC9Demangler;
059import java.lang.reflect.Type;
060import static org.bridj.Pointer.*;
061import static org.bridj.util.AnnotationUtils.*;
062
063import java.util.Collection;
064import org.bridj.Platform.DeleteFiles;
065import org.bridj.demangling.Demangler;
066import org.bridj.util.ProcessUtils;
067import org.bridj.util.StringUtils;
068
069/**
070 * Representation of a native shared library, with symbols retrieval / matching
071 * facilities.<br>
072 * This class is not meant to be used by end users, it's used by pluggable
073 * runtimes instead.
074 *
075 * @author ochafik
076 */
077public class NativeLibrary {
078
079    volatile long handle, symbols;
080    String path;
081    final File canonicalFile;
082    //Map<Class<?>, long[]> callbacks = new HashMap<Class<?>, long[]>();
083    NativeEntities nativeEntities = new NativeEntities();
084    Map<Long, Symbol> addrToName;
085    Map<String, Symbol> nameToSym;
086//      Map<String, Long> nameToAddr;
087
088    protected NativeLibrary(String path, long handle, long symbols) throws IOException {
089        this.path = path;
090        this.handle = handle;
091        this.symbols = symbols;
092        this.canonicalFile = path == null ? null : new File(path).getCanonicalFile();
093
094        Platform.addNativeLibrary(this);
095    }
096
097    long getSymbolsHandle() {
098        return symbols;
099    }
100
101    NativeEntities getNativeEntities() {
102        return nativeEntities;
103    }
104
105    static String followGNULDScript(String path) {
106        try {
107            Reader r = new FileReader(path);
108            try {
109                char c;
110                while ((c = (char) r.read()) == ' ' || c == '\t' || c == '\n') {
111                }
112                if (c == '/' && r.read() == '*') {
113                    BufferedReader br = new BufferedReader(r);
114                    r = br;
115                    String line;
116                    StringBuilder b = new StringBuilder("/*");
117                    while ((line = br.readLine()) != null) {
118                        b.append(line).append('\n');
119                    }
120                    String src = b.toString();
121                    Pattern ldGroupPattern = Pattern.compile("GROUP\\s*\\(\\s*([^\\s)]+)[^)]*\\)");
122                    Matcher m = ldGroupPattern.matcher(src);
123                    if (m.find()) {
124                        String actualPath = m.group(1);
125                        if (BridJ.verbose) {
126                            BridJ.info("Parsed LD script '" + path + "', found absolute reference to '" + actualPath + "'");
127                        }
128                        return actualPath;
129                    } else {
130                        BridJ.error("Failed to parse LD script '" + path + "' !");
131                    }
132                }
133            } finally {
134                r.close();
135            }
136        } catch (Throwable th) {
137            BridJ.error("Unexpected error: " + th, th);
138        }
139        return path;
140    }
141
142    public static NativeLibrary load(String path) throws IOException {
143        long handle = 0;
144        File file = new File(path);
145        boolean exists = file.exists();
146        if (file.isAbsolute() && !exists) {
147            return null;
148        }
149
150        if (Platform.isUnix() && exists) {
151            path = followGNULDScript(path);
152        }
153
154        handle = JNI.loadLibrary(path);
155        if (handle == 0) {
156            return null;
157        }
158        long symbols = JNI.loadLibrarySymbols(path);
159        return new NativeLibrary(path, handle, symbols);
160    }
161
162    /*public boolean methodMatchesSymbol(Class<?> declaringClass, Method method, String symbol) {
163     return symbol.contains(method.getName()) && symbol.contains(declaringClass.getSimpleName());
164     }*/
165    long getHandle() {
166        if (path != null && handle == 0) {
167            throw new RuntimeException("Library was released and cannot be used anymore");
168        }
169        return handle;
170    }
171
172    @Override
173    protected void finalize() throws Throwable {
174        release();
175    }
176
177    public synchronized void release() {
178        if (handle == 0) {
179            return;
180        }
181
182        if (BridJ.verbose) {
183            BridJ.info("Releasing library '" + path + "'");
184        }
185
186        nativeEntities.release();
187
188        JNI.freeLibrarySymbols(symbols);
189        JNI.freeLibrary(handle);
190        handle = 0;
191
192        if (canonicalFile != null && Platform.temporaryExtractedLibraryCanonicalFiles.remove(canonicalFile)) {
193            if (canonicalFile.delete()) {
194                if (BridJ.verbose) {
195                    BridJ.info("Deleted temporary library file '" + canonicalFile + "'");
196                }
197            } else {
198                BridJ.error("Failed to delete temporary library file '" + canonicalFile + "'");
199            }
200        }
201
202    }
203
204    public Pointer<?> getSymbolPointer(String name) {
205        return pointerToAddress(getSymbolAddress(name));
206    }
207
208    public long getSymbolAddress(String name) {
209        if (nameToSym != null) {
210            Symbol addr = nameToSym.get(name);
211//                      long addr = nameToAddr.get(name);
212//                      if (addr != 0)
213            if (addr != null) {
214                return addr.getAddress();
215            }
216        }
217        long address = JNI.findSymbolInLibrary(getHandle(), name);
218        if (address == 0) {
219            address = JNI.findSymbolInLibrary(getHandle(), "_" + name);
220        }
221        return address;
222    }
223
224    public synchronized Symbol getSymbol(AnnotatedElement member) throws FileNotFoundException {
225        org.bridj.ann.Symbol mg = getAnnotation(org.bridj.ann.Symbol.class, member);
226        String name = null;
227
228        Name nameAnn = member.getAnnotation(Name.class);
229        if (nameAnn != null) {
230            name = nameAnn.value();
231        } else if (member instanceof Member) {
232            name = ((Member) member).getName();
233        }
234
235        List<String> names = new ArrayList<String>();
236        if (mg != null) {
237            names.addAll(Arrays.asList(mg.value()));
238        }
239        if (name != null) {
240            names.add(name);
241        }
242
243        for (String n : names) {
244            Symbol handle = getSymbol(n);
245            if (handle == null) {
246                handle = getSymbol("_" + n);
247            }
248            if (handle == null) {
249                handle = getSymbol(n + (Platform.useUnicodeVersionOfWindowsAPIs ? "W" : "A"));
250            }
251            if (handle != null) {
252                return handle;
253            }
254        }
255
256        if (member instanceof Method) {
257            Method method = (Method) member;
258            for (Demangler.Symbol symbol : getSymbols()) {
259                if (symbol.matches(method)) {
260                    return symbol;
261                }
262            }
263        }
264        return null;
265    }
266
267    public boolean isMSVC() {
268        return Platform.isWindows();
269    }
270
271    /**
272     * Filter for symbols
273     */
274    public interface SymbolAccepter {
275
276        boolean accept(Symbol symbol);
277    }
278
279    public Symbol getFirstMatchingSymbol(SymbolAccepter accepter) {
280        for (Symbol symbol : getSymbols()) {
281            if (accepter.accept(symbol)) {
282                return symbol;
283            }
284        }
285        return null;
286    }
287
288    public Collection<Demangler.Symbol> getSymbols() {
289        try {
290            scanSymbols();
291        } catch (Exception ex) {
292            assert BridJ.error("Failed to scan symbols of library '" + path + "'", ex);
293        }
294        return nameToSym == null ? Collections.EMPTY_LIST : Collections.unmodifiableCollection(nameToSym.values());
295    }
296
297    public String getSymbolName(long address) {
298        if (addrToName == null && getSymbolsHandle() != 0)//Platform.isUnix())
299        {
300            return JNI.findSymbolName(getHandle(), getSymbolsHandle(), address);
301        }
302
303        Demangler.Symbol symbol = getSymbol(address);
304        return symbol == null ? null : symbol.getSymbol();
305    }
306
307    public Symbol getSymbol(long address) {
308        try {
309            scanSymbols();
310            Symbol symbol = addrToName.get(address);
311            return symbol;
312        } catch (Exception ex) {
313            throw new RuntimeException("Failed to get name of address " + address, ex);
314        }
315    }
316
317    public Symbol getSymbol(String name) {
318        try {
319            Symbol symbol;
320            long addr;
321
322            if (nameToSym == null) {// symbols not scanned yet, try without them !
323                addr = JNI.findSymbolInLibrary(getHandle(), name);
324                if (addr != 0) {
325                    symbol = new Symbol(name, this);
326                    symbol.setAddress(addr);
327                    return symbol;
328                }
329            }
330            scanSymbols();
331            if (nameToSym == null) {
332                return null;
333            }
334
335            symbol = nameToSym.get(name);
336            if (addrToName == null) {
337                if (symbol == null) {
338                    addr = JNI.findSymbolInLibrary(getHandle(), name);
339                    if (addr != 0) {
340                        symbol = new Symbol(name, this);
341                        symbol.setAddress(addr);
342                        nameToSym.put(name, symbol);
343                    }
344                }
345            }
346            return symbol;
347        } catch (Exception ex) {
348            ex.printStackTrace();
349            return null;
350//                      throw new RuntimeException("Failed to get symbol " + name, ex);
351        }
352    }
353
354    void scanSymbols() throws Exception {
355        if (addrToName != null) {
356            return;
357        }
358
359        nameToSym = new HashMap<String, Symbol>();
360//              nameToAddr = new HashMap<String, Long>();
361
362        String[] symbs = null;
363        if (symbs == null) {
364            //System.out.println("Calling getLibrarySymbols");
365            symbs = JNI.getLibrarySymbols(getHandle(), getSymbolsHandle());
366            //System.out.println("Got " + symbs + " (" + (symbs == null ? "null" : symbs.length + "") + ")");
367        }
368
369        if (symbs == null) {
370            return;
371        }
372
373        addrToName = new HashMap<Long, Demangler.Symbol>();
374
375        boolean is32 = !Platform.is64Bits();
376        for (String name : symbs) {
377            if (name == null) {
378                continue;
379            }
380
381            long addr = JNI.findSymbolInLibrary(getHandle(), name);
382            if (addr == 0 && name.startsWith("_")) {
383                String n2 = name.substring(1);
384                addr = JNI.findSymbolInLibrary(getHandle(), n2);
385                if (addr == 0) {
386                    n2 = "_" + name;
387                    addr = JNI.findSymbolInLibrary(getHandle(), n2);
388                }
389                if (addr != 0) {
390                    name = n2;
391                }
392
393            }
394            if (addr == 0) {
395                if (BridJ.verbose) {
396                    BridJ.warning("Symbol '" + name + "' not found.");
397                }
398                continue;
399            }
400            //if (is32)
401            //  addr = addr & 0xffffffffL;
402            //System.out.println("Symbol " + Long.toHexString(addr) + " = '" + name + "'");
403
404            Symbol sym = new Demangler.Symbol(name, this);
405            sym.setAddress(addr);
406            addrToName.put(addr, sym);
407            nameToSym.put(name, sym);
408            //nameToAddr.put(name, addr);
409            //System.out.println("'" + name + "' = \t" + TestCPP.hex(addr) + "\n\t" + sym.getParsedRef());
410        }
411        if (BridJ.debug) {
412            BridJ.info("Found " + nameToSym.size() + " symbols in '" + path + "' :");
413
414            for (Symbol sym : nameToSym.values()) {
415                BridJ.info("DEBUG(BridJ): library=\"" + path + "\", symbol=\"" + sym.getSymbol() + "\", address=" + Long.toHexString(sym.getAddress()) + ", demangled=\"" + sym.getParsedRef() + "\"");
416            }
417
418            //for (Symbol sym : nameToSym.values())
419            //  System.out.println("Symbol '" + sym + "' = " + sym.getParsedRef());
420        }
421    }
422
423    public MemberRef parseSymbol(String symbol) throws DemanglingException {
424        if ("__cxa_pure_virtual".equals(symbol)) {
425            return null;
426        }
427
428        if (Platform.isWindows()) {
429            try {
430                MemberRef result = new VC9Demangler(this, symbol).parseSymbol();
431                if (result != null) {
432                    return result;
433                }
434            } catch (Throwable th) {
435            }
436        }
437        return new GCC4Demangler(this, symbol).parseSymbol();
438    }
439}