IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> so文件的加载分析 -> 正文阅读

[移动开发]so文件的加载分析

在Android中,有个加载so的逻辑,System.LoadLibrary(“so文件名”);
Android版本为9.0.0,代码来自下面的网址
androidxref.com/9.0.0_r3/
我们直接定位如下方法
因为是静态方法,所以可以直接调用

1667    @CallerSensitive
1668    public static void loadLibrary(String libname) {
1669        Runtime.getRuntime().loadLibrary0(VMStack.getCallingClassLoader(), libname);
1670    }

我们看下这个loadLibrary0方法,方法有两个参数。

991    public void loadLibrary(String libname, ClassLoader classLoader) {
992        checkTargetSdkVersionForLoad("java.lang.Runtime#loadLibrary(String, ClassLoader)");
993        java.lang.System.logE("java.lang.Runtime#loadLibrary(String, ClassLoader)" +
994                              " is private and will be removed in a future Android release");
995        loadLibrary0(classLoader, libname);
996    }
997
998    synchronized void loadLibrary0(ClassLoader loader, String libname) {
999        if (libname.indexOf((int)File.separatorChar) != -1) {
1000            throw new UnsatisfiedLinkError(
1001    "Directory separator should not appear in library name: " + libname);
1002        }
1003        String libraryName = libname;
1004        if (loader != null) {
1005            String filename = loader.findLibrary(libraryName);
1006            if (filename == null) {
1007                // It's not necessarily true that the ClassLoader used
1008                // System.mapLibraryName, but the default setup does, and it's
1009                // misleading to say we didn't find "libMyLibrary.so" when we
1010                // actually searched for "liblibMyLibrary.so.so".
1011                throw new UnsatisfiedLinkError(loader + " couldn't find \"" +
1012                                               System.mapLibraryName(libraryName) + "\"");
1013            }
1014            String error = nativeLoad(filename, loader);
1015            if (error != null) {
1016                throw new UnsatisfiedLinkError(error);
1017            }
1018            return;
1019        }
1020
1021        String filename = System.mapLibraryName(libraryName);
1022        List<String> candidates = new ArrayList<String>();
1023        String lastError = null;
1024        for (String directory : getLibPaths()) {
1025            String candidate = directory + filename;
1026            candidates.add(candidate);
1027
1028            if (IoUtils.canOpenReadOnly(candidate)) {
1029                String error = nativeLoad(candidate, loader);
1030                if (error == null) {
1031                    return; // We successfully loaded the library. Job done.
1032                }
1033                lastError = error;
1034            }
1035        }
1036
1037        if (lastError != null) {
1038            throw new UnsatisfiedLinkError(lastError);
1039        }
1040        throw new UnsatisfiedLinkError("Library " + libraryName + " not found; tried " + candidates);
1041    }

如下代码获取文件名字,

1005            String filename = loader.findLibrary(libraryName);

文件名字不为空调用如下代码

1014            String error = nativeLoad(filename, loader);

如果error不为空直接抛出异常。不为空直接返回

1015            if (error != null) {
1016                throw new UnsatisfiedLinkError(error);
1017            }
1018            return;

下面的代码是没有ClassLoader 的情况下执行的。
相关逻辑基本差不多。
获取so的文件名

String filename = System.mapLibraryName(libraryName);

调用本地方法,加载so文件。

String error = nativeLoad(candidate, loader);

error为空直接返回。

1030                if (error == null) {
1031                    return; // We successfully loaded the library. Job done.
1032                }
1021        String filename = System.mapLibraryName(libraryName);
1022        List<String> candidates = new ArrayList<String>();
1023        String lastError = null;
1024        for (String directory : getLibPaths()) {
1025            String candidate = directory + filename;
1026            candidates.add(candidate);
1027
1028            if (IoUtils.canOpenReadOnly(candidate)) {
1029                String error = nativeLoad(candidate, loader);
1030                if (error == null) {
1031                    return; // We successfully loaded the library. Job done.
1032                }
1033                lastError = error;
1034            }
1035        }
1036
1037        if (lastError != null) {
1038            throw new UnsatisfiedLinkError(lastError);
1039        }
1040        throw new UnsatisfiedLinkError("Library " + libraryName + " not found; tried " + candidates);
1041    }

我们跟踪一下如下方法

 nativeLoad(candidate, loader);
 nativeLoad(filename, loader);

如下方法是本地方法,逻辑用c或c++编写

1071    private static native String nativeLoad(String filename, ClassLoader loader);

这个本地方法在如下文件里

 xref: /libcore/ojluni/src/main/native/Runtime.c 

如下代码可以知道,这个本地方法是动态注册的。

83static JNINativeMethod gMethods[] = {
84  FAST_NATIVE_METHOD(Runtime, freeMemory, "()J"),
85  FAST_NATIVE_METHOD(Runtime, totalMemory, "()J"),
86  FAST_NATIVE_METHOD(Runtime, maxMemory, "()J"),
87  NATIVE_METHOD(Runtime, gc, "()V"),
88  NATIVE_METHOD(Runtime, nativeExit, "(I)V"),
89  NATIVE_METHOD(Runtime, nativeLoad,
90                "(Ljava/lang/String;Ljava/lang/ClassLoader;)"
91                    "Ljava/lang/String;"),
92};

跟踪一下JVM_NativeLoad函数
该函数有三个参数

76JNIEXPORT jstring JNICALL
77Runtime_nativeLoad(JNIEnv* env, jclass ignored, jstring javaFilename,
78                   jobject javaLoader)
79{
80    return JVM_NativeLoad(env, javaFilename, javaLoader);
81}

如下是JVM_NativeLoad函数

323JNIEXPORT jstring JVM_NativeLoad(JNIEnv* env,
324                                 jstring javaFilename,
325                                 jobject javaLoader) {
326  ScopedUtfChars filename(env, javaFilename);
327  if (filename.c_str() == NULL) {
328    return NULL;
329  }
330
331  std::string error_msg;
332  {
333    art::JavaVMExt* vm = art::Runtime::Current()->GetJavaVM();
334    bool success = vm->LoadNativeLibrary(env,
335                                         filename.c_str(),
336                                         javaLoader,
337                                         &error_msg);
338    if (success) {
339      return nullptr;
340    }
341  }
342
343  // Don't let a pending exception from JNI_OnLoad cause a CheckJNI issue with NewStringUTF.
344  env->ExceptionClear();
345  return env->NewStringUTF(error_msg.c_str());
346}

我们看一下如下代码

334    bool success = vm->LoadNativeLibrary(env,
335                                         filename.c_str(),
336                                         javaLoader,
337                                         &error_msg);

跟踪一下LoadNativeLibrary函数

854bool JavaVMExt::LoadNativeLibrary(JNIEnv* env,
855                                  const std::string& path,
856                                  jobject class_loader,
857                                  std::string* error_msg) {
858  error_msg->clear();
859
860  // See if we've already loaded this library.  If we have, and the class loader
861  // matches, return successfully without doing anything.
862  // TODO: for better results we should canonicalize the pathname (or even compare
863  // inodes). This implementation is fine if everybody is using System.loadLibrary.
864  SharedLibrary* library;
865  Thread* self = Thread::Current();
866  {
867    // TODO: move the locking (and more of this logic) into Libraries.
868    MutexLock mu(self, *Locks::jni_libraries_lock_);
869    library = libraries_->Get(path);
870  }
871  void* class_loader_allocator = nullptr;
872  {
873    ScopedObjectAccess soa(env);
874    // As the incoming class loader is reachable/alive during the call of this function,
875    // it's okay to decode it without worrying about unexpectedly marking it alive.
876    ObjPtr<mirror::ClassLoader> loader = soa.Decode<mirror::ClassLoader>(class_loader);
877
878    ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
879    if (class_linker->IsBootClassLoader(soa, loader.Ptr())) {
880      loader = nullptr;
881      class_loader = nullptr;
882    }
883
884    class_loader_allocator = class_linker->GetAllocatorForClassLoader(loader.Ptr());
885    CHECK(class_loader_allocator != nullptr);
886  }
887  if (library != nullptr) {
888    // Use the allocator pointers for class loader equality to avoid unnecessary weak root decode.
889    if (library->GetClassLoaderAllocator() != class_loader_allocator) {
890      // The library will be associated with class_loader. The JNI
891      // spec says we can't load the same library into more than one
892      // class loader.
893      //
894      // This isn't very common. So spend some time to get a readable message.
895      auto call_to_string = [&](jobject obj) -> std::string {
896        if (obj == nullptr) {
897          return "null";
898        }
899        // Handle jweaks. Ignore double local-ref.
900        ScopedLocalRef<jobject> local_ref(env, env->NewLocalRef(obj));
901        if (local_ref != nullptr) {
902          ScopedLocalRef<jclass> local_class(env, env->GetObjectClass(local_ref.get()));
903          jmethodID to_string = env->GetMethodID(local_class.get(),
904                                                 "toString",
905                                                 "()Ljava/lang/String;");
906          DCHECK(to_string != nullptr);
907          ScopedLocalRef<jobject> local_string(env,
908                                               env->CallObjectMethod(local_ref.get(), to_string));
909          if (local_string != nullptr) {
910            ScopedUtfChars utf(env, reinterpret_cast<jstring>(local_string.get()));
911            if (utf.c_str() != nullptr) {
912              return utf.c_str();
913            }
914          }
915          env->ExceptionClear();
916          return "(Error calling toString)";
917        }
918        return "null";
919      };
920      std::string old_class_loader = call_to_string(library->GetClassLoader());
921      std::string new_class_loader = call_to_string(class_loader);
922      StringAppendF(error_msg, "Shared library \"%s\" already opened by "
923          "ClassLoader %p(%s); can't open in ClassLoader %p(%s)",
924          path.c_str(),
925          library->GetClassLoader(),
926          old_class_loader.c_str(),
927          class_loader,
928          new_class_loader.c_str());
929      LOG(WARNING) << *error_msg;
930      return false;
931    }
932    VLOG(jni) << "[Shared library \"" << path << "\" already loaded in "
933              << " ClassLoader " << class_loader << "]";
934    if (!library->CheckOnLoadResult()) {
935      StringAppendF(error_msg, "JNI_OnLoad failed on a previous attempt "
936          "to load \"%s\"", path.c_str());
937      return false;
938    }
939    return true;
940  }
941
942  // Open the shared library.  Because we're using a full path, the system
943  // doesn't have to search through LD_LIBRARY_PATH.  (It may do so to
944  // resolve this library's dependencies though.)
945
946  // Failures here are expected when java.library.path has several entries
947  // and we have to hunt for the lib.
948
949  // Below we dlopen but there is no paired dlclose, this would be necessary if we supported
950  // class unloading. Libraries will only be unloaded when the reference count (incremented by
951  // dlopen) becomes zero from dlclose.
952
953  // Retrieve the library path from the classloader, if necessary.
954  ScopedLocalRef<jstring> library_path(env, GetLibrarySearchPath(env, class_loader));
955
956  Locks::mutator_lock_->AssertNotHeld(self);
957  const char* path_str = path.empty() ? nullptr : path.c_str();
958  bool needs_native_bridge = false;
959  void* handle = android::OpenNativeLibrary(env,
960                                            runtime_->GetTargetSdkVersion(),
961                                            path_str,
962                                            class_loader,
963                                            library_path.get(),
964                                            &needs_native_bridge,
965                                            error_msg);
966
967  VLOG(jni) << "[Call to dlopen(\"" << path << "\", RTLD_NOW) returned " << handle << "]";
968
969  if (handle == nullptr) {
970    VLOG(jni) << "dlopen(\"" << path << "\", RTLD_NOW) failed: " << *error_msg;
971    return false;
972  }
973
974  if (env->ExceptionCheck() == JNI_TRUE) {
975    LOG(ERROR) << "Unexpected exception:";
976    env->ExceptionDescribe();
977    env->ExceptionClear();
978  }
979  // Create a new entry.
980  // TODO: move the locking (and more of this logic) into Libraries.
981  bool created_library = false;
982  {
983    // Create SharedLibrary ahead of taking the libraries lock to maintain lock ordering.
984    std::unique_ptr<SharedLibrary> new_library(
985        new SharedLibrary(env,
986                          self,
987                          path,
988                          handle,
989                          needs_native_bridge,
990                          class_loader,
991                          class_loader_allocator));
992
993    MutexLock mu(self, *Locks::jni_libraries_lock_);
994    library = libraries_->Get(path);
995    if (library == nullptr) {  // We won race to get libraries_lock.
996      library = new_library.release();
997      libraries_->Put(path, library);
998      created_library = true;
999    }
1000  }
1001  if (!created_library) {
1002    LOG(INFO) << "WOW: we lost a race to add shared library: "
1003        << "\"" << path << "\" ClassLoader=" << class_loader;
1004    return library->CheckOnLoadResult();
1005  }
1006  VLOG(jni) << "[Added shared library \"" << path << "\" for ClassLoader " << class_loader << "]";
1007
1008  bool was_successful = false;
1009  void* sym = library->FindSymbol("JNI_OnLoad", nullptr);
1010  if (sym == nullptr) {
1011    VLOG(jni) << "[No JNI_OnLoad found in \"" << path << "\"]";
1012    was_successful = true;
1013  } else {
1014    // Call JNI_OnLoad.  We have to override the current class
1015    // loader, which will always be "null" since the stuff at the
1016    // top of the stack is around Runtime.loadLibrary().  (See
1017    // the comments in the JNI FindClass function.)
1018    ScopedLocalRef<jobject> old_class_loader(env, env->NewLocalRef(self->GetClassLoaderOverride()));
1019    self->SetClassLoaderOverride(class_loader);
1020
1021    VLOG(jni) << "[Calling JNI_OnLoad in \"" << path << "\"]";
1022    typedef int (*JNI_OnLoadFn)(JavaVM*, void*);
1023    JNI_OnLoadFn jni_on_load = reinterpret_cast<JNI_OnLoadFn>(sym);
1024    int version = (*jni_on_load)(this, nullptr);
1025
1026    if (runtime_->GetTargetSdkVersion() != 0 && runtime_->GetTargetSdkVersion() <= 21) {
1027      // Make sure that sigchain owns SIGSEGV.
1028      EnsureFrontOfChain(SIGSEGV);
1029    }
1030
1031    self->SetClassLoaderOverride(old_class_loader.get());
1032
1033    if (version == JNI_ERR) {
1034      StringAppendF(error_msg, "JNI_ERR returned from JNI_OnLoad in \"%s\"", path.c_str());
1035    } else if (JavaVMExt::IsBadJniVersion(version)) {
1036      StringAppendF(error_msg, "Bad JNI version returned from JNI_OnLoad in \"%s\": %d",
1037                    path.c_str(), version);
1038      // It's unwise to call dlclose() here, but we can mark it
1039      // as bad and ensure that future load attempts will fail.
1040      // We don't know how far JNI_OnLoad got, so there could
1041      // be some partially-initialized stuff accessible through
1042      // newly-registered native method calls.  We could try to
1043      // unregister them, but that doesn't seem worthwhile.
1044    } else {
1045      was_successful = true;
1046    }
1047    VLOG(jni) << "[Returned " << (was_successful ? "successfully" : "failure")
1048              << " from JNI_OnLoad in \"" << path << "\"]";
1049  }
1050
1051  library->SetResult(was_successful);
1052  return was_successful;
1053}

定义一个共享库指针,用于存放so文件句柄

SharedLibrary* library;

调用Get函数,传入path,获得so文件指针

library = libraries_->Get(path);

如下是SharedLibrary类

67class SharedLibrary {
68 public:
69  SharedLibrary(JNIEnv* env, Thread* self, const std::string& path, void* handle,
70                bool needs_native_bridge, jobject class_loader, void* class_loader_allocator)
71      : path_(path),
72        handle_(handle),
73        needs_native_bridge_(needs_native_bridge),
74        class_loader_(env->NewWeakGlobalRef(class_loader)),
75        class_loader_allocator_(class_loader_allocator),
76        jni_on_load_lock_("JNI_OnLoad lock"),
77        jni_on_load_cond_("JNI_OnLoad condition variable", jni_on_load_lock_),
78        jni_on_load_thread_id_(self->GetThreadId()),
79        jni_on_load_result_(kPending) {
80    CHECK(class_loader_allocator_ != nullptr);
81  }
82
83  ~SharedLibrary() {
84    Thread* self = Thread::Current();
85    if (self != nullptr) {
86      self->GetJniEnv()->DeleteWeakGlobalRef(class_loader_);
87    }
88
89    android::CloseNativeLibrary(handle_, needs_native_bridge_);
90  }
91
92  jweak GetClassLoader() const {
93    return class_loader_;
94  }
95
96  const void* GetClassLoaderAllocator() const {
97    return class_loader_allocator_;
98  }
99
100  const std::string& GetPath() const {
101    return path_;
102  }
103
104  /*
105   * Check the result of an earlier call to JNI_OnLoad on this library.
106   * If the call has not yet finished in another thread, wait for it.
107   */
108  bool CheckOnLoadResult()
109      REQUIRES(!jni_on_load_lock_) {
110    Thread* self = Thread::Current();
111    bool okay;
112    {
113      MutexLock mu(self, jni_on_load_lock_);
114
115      if (jni_on_load_thread_id_ == self->GetThreadId()) {
116        // Check this so we don't end up waiting for ourselves.  We need to return "true" so the
117        // caller can continue.
118        LOG(INFO) << *self << " recursive attempt to load library " << "\"" << path_ << "\"";
119        okay = true;
120      } else {
121        while (jni_on_load_result_ == kPending) {
122          VLOG(jni) << "[" << *self << " waiting for \"" << path_ << "\" " << "JNI_OnLoad...]";
123          jni_on_load_cond_.Wait(self);
124        }
125
126        okay = (jni_on_load_result_ == kOkay);
127        VLOG(jni) << "[Earlier JNI_OnLoad for \"" << path_ << "\" "
128            << (okay ? "succeeded" : "failed") << "]";
129      }
130    }
131    return okay;
132  }
133
134  void SetResult(bool result) REQUIRES(!jni_on_load_lock_) {
135    Thread* self = Thread::Current();
136    MutexLock mu(self, jni_on_load_lock_);
137
138    jni_on_load_result_ = result ? kOkay : kFailed;
139    jni_on_load_thread_id_ = 0;
140
141    // Broadcast a wakeup to anybody sleeping on the condition variable.
142    jni_on_load_cond_.Broadcast(self);
143  }
144
145  void SetNeedsNativeBridge(bool needs) {
146    needs_native_bridge_ = needs;
147  }
148
149  bool NeedsNativeBridge() const {
150    return needs_native_bridge_;
151  }
152
153  // No mutator lock since dlsym may block for a while if another thread is doing dlopen.
154  void* FindSymbol(const std::string& symbol_name, const char* shorty = nullptr)
155      REQUIRES(!Locks::mutator_lock_) {
156    return NeedsNativeBridge()
157        ? FindSymbolWithNativeBridge(symbol_name.c_str(), shorty)
158        : FindSymbolWithoutNativeBridge(symbol_name.c_str());
159  }
160
161  // No mutator lock since dlsym may block for a while if another thread is doing dlopen.
162  void* FindSymbolWithoutNativeBridge(const std::string& symbol_name)
163      REQUIRES(!Locks::mutator_lock_) {
164    CHECK(!NeedsNativeBridge());
165
166    return dlsym(handle_, symbol_name.c_str());
167  }
168
169  void* FindSymbolWithNativeBridge(const std::string& symbol_name, const char* shorty)
170      REQUIRES(!Locks::mutator_lock_) {
171    CHECK(NeedsNativeBridge());
172
173    uint32_t len = 0;
174    return android::NativeBridgeGetTrampoline(handle_, symbol_name.c_str(), shorty, len);
175  }
176
177 private:
178  enum JNI_OnLoadState {
179    kPending,
180    kFailed,
181    kOkay,
182  };
183
184  // Path to library "/system/lib/libjni.so".
185  const std::string path_;
186
187  // The void* returned by dlopen(3).
188  void* const handle_;
189
190  // True if a native bridge is required.
191  bool needs_native_bridge_;
192
193  // The ClassLoader this library is associated with, a weak global JNI reference that is
194  // created/deleted with the scope of the library.
195  const jweak class_loader_;
196  // Used to do equality check on class loaders so we can avoid decoding the weak root and read
197  // barriers that mess with class unloading.
198  const void* class_loader_allocator_;
199
200  // Guards remaining items.
201  Mutex jni_on_load_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER;
202  // Wait for JNI_OnLoad in other thread.
203  ConditionVariable jni_on_load_cond_ GUARDED_BY(jni_on_load_lock_);
204  // Recursive invocation guard.
205  uint32_t jni_on_load_thread_id_ GUARDED_BY(jni_on_load_lock_);
206  // Result of earlier JNI_OnLoad call.
207  JNI_OnLoadState jni_on_load_result_ GUARDED_BY(jni_on_load_lock_);
208};

如下函数打开so文件,返回文件句柄
有文件句柄后可以对so文件随意读写操作

959  void* handle = android::OpenNativeLibrary(env,
960                                            runtime_->GetTargetSdkVersion(),
961                                            path_str,
962                                            class_loader,
963                                            library_path.get(),
964                                            &needs_native_bridge,
965                                            error_msg);

如下函数查找JNI_Onload的位置,用于远程调用。
因为so文件本身是可执行文件,
在ida里可以看到函数等

1009  void* sym = library->FindSymbol("JNI_OnLoad", nullptr);

如下函数把JNI_Onload位置转为可调用的函数

1023    JNI_OnLoadFn jni_on_load = reinterpret_cast<JNI_OnLoadFn>(sym);

开始调用,返回version

1024    int version = (*jni_on_load)(this, nullptr);

通过version判断是否成功调用该函数

 if (version == JNI_ERR) {
1034      StringAppendF(error_msg, "JNI_ERR returned from JNI_OnLoad in \"%s\"", path.c_str());
1035    } else if (JavaVMExt::IsBadJniVersion(version)) {
1036      StringAppendF(error_msg, "Bad JNI version returned from JNI_OnLoad in \"%s\": %d",
1037                    path.c_str(), version);
1038      // It's unwise to call dlclose() here, but we can mark it
1039      // as bad and ensure that future load attempts will fail.
1040      // We don't know how far JNI_OnLoad got, so there could
1041      // be some partially-initialized stuff accessible through
1042      // newly-registered native method calls.  We could try to
1043      // unregister them, but that doesn't seem worthwhile.
1044    } else {
1045      was_successful = true;
1046    }

如果执行了如下代码,说明lib库加载完成。

1052  return was_successful;
  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-10-08 20:53:18  更:2022-10-08 20:56:00 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 23:29:01-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码