由于技术能力有限,本文的部分观点可能描述的并非准确,欢迎指正
在早期Android系统源码编译环境中,我们想要编译一个apk需要将java源码,资源文件等同时放在一个工程文件夹中,并且配置好相应的mk文件后再通过mmm
命令触发jack
工具链进行对工程源码的编译。随着团队的扩大以及各个项目的独立,我们开始尝试将独立项目的apk预置到Android系统源码中,让系统只需要对apk文件进行重新打包即可,无需再将java
源码上传到系统中,减小了独立项目和系统之间的关联,让独立项目更加快速的迭代和更独立的发展与管理。
由于apk从系统中独立了出来,与此同时也给apk增加了更多的三方应用库。而在众多的三方库中有不少的三方库都有着自己的so文件。当apk在系统中重新被编译时会对so文件重新打包并放入system/lib
目录中,由此引发了so文件内在做dlopen
时出现的权限问题。
04-06 12:48:46.004 7821-7821/com.android.browser E/linker: library "/system/app/Browser/lib/arm/libXXwebview.so" ("/system/lib/libXXwebview.so") needed or dlopened by "/system/lib/libnativeloader.so" is not accessible for the namespace: [name="classloader-namespace", ld_library_paths="", default_library_paths="/system/app/Browser/lib/arm", permitted_paths="/data:/mnt/expand"]
04-06 12:48:46.005 7821-7821/com.android.browser E/cr_LibraryLoader: Unable to load library: qihoowebview
04-06 12:48:46.005 7821-7821/com.android.browser W/System.err: java.lang.reflect.InvocationTargetException
at java.lang.reflect.Constructor.newInstance0(Native Method)
at java.lang.reflect.Constructor.newInstance(Constructor.java:334)
at com.qihoo.webkit.WebViewFactory.getProvider(WebViewFactory.java:95)
at com.qihoo.webkit.internal.QhAdapter$QhWebViewInitializationController.getInstance(QhAdapter.java:119)
at com.qihoo.webkit.internal.QhAdapter.useSyeWebView(QhAdapter.java:311)
at com.qihoo.webkit.CookieManager.getInstance(CookieManager.java:41)
at com.meitu.mobile.browser.lib.webkit.g.a(MtCookieManager.java:26)
at com.android.browser.o.am(BrowserSettings.java:398)
at com.android.browser.o.an(BrowserSettings.java:408)
at com.android.browser.o.onSharedPreferenceChanged(BrowserSettings.java:426)
at android.app.SharedPreferencesImpl$EditorImpl.notifyListeners(SharedPreferencesImpl.java:561)
04-06 12:48:46.006 7821-7821/com.android.browser W/System.err: at android.app.SharedPreferencesImpl$EditorImpl.-wrap0(Unknown Source:0)
at android.app.SharedPreferencesImpl$EditorImpl$3.run(SharedPreferencesImpl.java:569)
at android.os.Handler.handleCallback(Handler.java:790)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:171)
at android.app.ActivityThread.main(ActivityThread.java:6611)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
Caused by: java.lang.RuntimeException: Cannot load WebView
at org.chromium.android_webview.AwBrowserProcess.loadLibrary(AwBrowserProcess.java:72)
at com.qihoo.webview.chromium.WebViewChromiumFactoryProvider.a(WebViewChromiumFactoryProvider.java:307)
at com.qihoo.webview.chromium.WebViewChromiumFactoryProvider.<init>(WebViewChromiumFactoryProvider.java:226)
... 20 more
Caused by: org.chromium.base.library_loader.ProcessInitException
at org.chromium.base.library_loader.LibraryLoader.loadAlreadyLocked(LibraryLoader.java:375)
at org.chromium.base.library_loader.LibraryLoader.loadNowOverrideApplicationContext(LibraryLoader.java:186)
at org.chromium.android_webview.AwBrowserProcess.loadLibrary(AwBrowserProcess.java:1170)
... 22 more
Caused by: java.lang.UnsatisfiedLinkError: dlopen failed: library "/system/app/Browser/lib/arm/libXXwebview.so" needed or dlopened by "/system/lib/libnativeloader.so" is not accessible for the namespace "classloader-namespace"
at java.lang.Runtime.loadLibrary0(Runtime.java:1016)
at java.lang.System.loadLibrary(System.java:1657)
at org.chromium.base.library_loader.LibraryLoader.loadAlreadyLocked(LibraryLoader.java:357)
... 24 more
04-06 12:48:46.007 7821-7821/com.android.browser E/WebViewFactory: TRACE_TAG_WEBVIEW
Android N
的行为变更中找到其中的缘由:NDK 应用链接至平台库。但是这个问题非常的奇怪,如果我们是 adb install
的方式,则可以正常的加载 libXXwebview.so
而 adb push
或者系统预置apk的方式则会出现该问题。
library "/system/app/Browser/lib/arm/libXXwebview.so" ("/system/lib/libXXwebview.so") needed or dlopened by "/system/lib/libnativeloader.so" is not accessible for the namespace: [name="classloader-namespace", ld_library_paths="", default_library_paths="/system/app/Browser/lib/arm", permitted_paths="/data:/mnt/expand"]
从上句log中我们可以很直接的明白问题的缘由,是加载libXXwebview.so
文件出错了。因为libXXwebview.so
文件内部依赖了libnativeloader.so
,但是在加载访问该依赖的so文件时却出现了权限不足的异常。既然权限不足那我们就尝试在系统源码中查看到底是为什么权限不足,是什么样的校验使得so文件的访问出现了异常。打开Android源码全局搜索 is not accessible for the namespace
本句异常的log,一探究竟。
linker.cpp
static bool load_library(android_namespace_t* ns,
LoadTask* task,
LoadTaskList* load_tasks,
int rtld_flags,
const std::string& realpath,
bool search_linked_namespaces) {
...
// do not check accessibility using realpath if fd is located on tmpfs
// this enables use of memfd_create() for apps
if ((fs_stat.f_type != TMPFS_MAGIC) && (!ns->is_accessible(realpath))) {
// TODO(dimitry): workaround for http://b/26394120 - the grey-list
// TODO(dimitry) before O release: add a namespace attribute to have this enabled
// only for classloader-namespaces
const soinfo* needed_by = task->is_dt_needed() ? task->get_needed_by() : nullptr;
if (is_greylisted(ns, name, needed_by)) {
// print warning only if needed by non-system library
if (needed_by == nullptr || !is_system_library(needed_by->get_realpath())) {
const soinfo* needed_or_dlopened_by = task->get_needed_by();
const char* sopath = needed_or_dlopened_by == nullptr ? "(unknown)" :
needed_or_dlopened_by->get_realpath();
DL_WARN_documented_change(__ANDROID_API_N__,
"private-api-enforced-for-api-level-24",
"library \"%s\" (\"%s\") needed or dlopened by \"%s\" "
"is not accessible by namespace \"%s\"",
name, realpath.c_str(), sopath, ns->get_name());
add_dlwarning(sopath, "unauthorized access to", name);
}
} else {
// do not load libraries if they are not accessible for the specified namespace.
const char* needed_or_dlopened_by = task->get_needed_by() == nullptr ?
"(unknown)" :
task->get_needed_by()->get_realpath();
DL_ERR("library \"%s\" needed or dlopened by \"%s\" is not accessible for the namespace \"%s\"",
name, needed_or_dlopened_by, ns->get_name());
// do not print this if a library is in the list of shared libraries for linked namespaces
if (!maybe_accessible_via_namespace_links(ns, name)) {
PRINT("library \"%s\" (\"%s\") needed or dlopened by \"%s\" is not accessible for the"
" namespace: [name=\"%s\", ld_library_paths=\"%s\", default_library_paths=\"%s\","
" permitted_paths=\"%s\"]",
name, realpath.c_str(),
needed_or_dlopened_by,
ns->get_name(),
android::base::Join(ns->get_ld_library_paths(), ':').c_str(),
android::base::Join(ns->get_default_library_paths(), ':').c_str(),
android::base::Join(ns->get_permitted_paths(), ':').c_str());
}
return false;
}
}
...
}
我们在AOSP\bionic\linker\linker.cpp
中发现了以上这段代码,从中我们明白了这段log的含义:
/system/app/Browser/lib/arm/libXXwebview.so
:表示我们要加载的模块(name
)/system/lib/libXXwebview.so
:表示该模块的真实文件地址(realpath
)classloader-namespace
:调用者的命名空间ld_library_paths
:已加载的library地址default_library_paths
:默认的library地址permitted_paths
:可被允许通过的library地址在函数(load_library()
)中,这段异常的log外部有一个if语句:
if ((fs_stat.f_type != TMPFS_MAGIC) && (!ns->is_accessible(realpath))) {...}
这个if语句主要表名了2个条件:
TMPFS_MAGIC
caller
)的命名空间是否可以访问即将要加载的so文件。(具体的函数内部是如何判断的,我们稍后分析)如果这个if条件满足了条件,那就表示这个调用者的命名空间是不能访问当前要加载的so文件。那么接下来就会获取当前这个so文件依赖的so文件needed_by
,接着会进入新的判断:
if (is_greylisted(ns, name, needed_by)) {...}
这个is_greylisted()
方法是非常有意思的,在NDK 应用链接至平台库这篇文章中,google提到:
为降低此限制可能对当前发布的应用的影响,面向 API 级别 23 或更低级别的应用在 Android N 上可暂时访问颇为常用的一组库,例如 libandroid_runtime.so、libcutils.so、libcrypto.so 和 libssl.so。如果您的应用加载其中某个库,logcat 会生成一个警告,并在目标设备上显示一个 Toast 来通知您。如果您看到这些警告,您应更新您的应用以添加该应用自己的库副本,或仅使用公开 NDK API。将来发布的 Android 平台可能会完全限制对私有库的使用,并导致您的应用崩溃。
这里的暂时访问颇为常用的一组库
就是指此处的灰名单了。我们一起来看看有哪些system的lib是在灰名单中。
linker.cpp
// TODO(dimitry): The grey-list is a workaround for http://b/26394120 ---
// gradually remove libraries from this list until it is gone.
static bool is_greylisted(android_namespace_t* ns, const char* name, const soinfo* needed_by) {
static const char* const kLibraryGreyList[] = {
"libandroid_runtime.so",
"libbinder.so",
"libcrypto.so",
"libcutils.so",
"libexpat.so",
"libgui.so",
"libmedia.so",
"libnativehelper.so",
"libssl.so",
"libstagefright.so",
"libsqlite.so",
"libui.so",
"libutils.so",
"libvorbisidec.so",
nullptr
};
// If you're targeting N, you don't get the greylist.
if (g_greylist_disabled || get_application_target_sdk_version() >= __ANDROID_API_N__) {
return false;
}
// if the library needed by a system library - implicitly assume it
// is greylisted unless it is in the list of shared libraries for one or
// more linked namespaces
if (needed_by != nullptr && is_system_library(needed_by->get_realpath())) {
return !maybe_accessible_via_namespace_links(ns, name);
}
// if this is an absolute path - make sure it points to /system/lib(64)
if (name[0] == '/' && dirname(name) == kSystemLibDir) {
// and reduce the path to basename
name = basename(name);
}
for (size_t i = 0; kLibraryGreyList[i] != nullptr; ++i) {
if (strcmp(name, kLibraryGreyList[i]) == 0) {
return true;
}
}
return false;
}
// END OF WORKAROUND
在kLibraryGreyList
中,则列出了所有灰名单的so文件。此时聪明的你可能会想到,我们是否可以将我们要加载的so文件libXXwebview.so
加入这个灰名单中,这样就可以通过了呢?答案是不行的,我们仔细观察这个is_greylisted()
方法,其中的验证路径有很多:
g_greylist_disabled 判断是否开启灰名单的机制。
判断调用的targetApi是否 小于Android N (caller Api < __ANDROID_API_N__
)
如果依赖的so文件是system的library,那么这个要加载的so文件是不存在于分享链接库中的,这样我们依赖的so文件则可以算是灰名单。
最后会判断这个so文件是否在灰名单数组中。
通过以上的层层判断,我们可以得到当前要加载的so文件是否为灰名单中的so。
如果是灰名单中的so文件,那么接下来则会开始判读我们一来的so文件是否为系统的so,如果不是系统的so文件,那么就会打印出警告语句,并且出现toast提示:unauthorized access to libXXwebview.so
反之,如果libXXwebview.so
不是灰名单中的so文件,并且这个so也不是共享列表的so文件,结果就出现了错误。
至此,我们对访问限制的分析以及初步完成,接下来,我们继续分析如何突破访问限制,使得我们的so文件变得可加载。
目前解决访问限制的方法有2个突破点:
ns->is_accessible(realpath)
:让命名空间的访问返回trueis_greylisted()
:增加灰名单根据之前对灰名单的验证分析以及google在文档的描述中我们可以知道,google在未来对灰名单的改动是比较大的,或者会去除灰名单,那么我们从灰名单中突破并不是最优的做法。那么我们则选择第一种方法,让命名空间的访问校验返回true.
我们可以在linker_namespaces.cpp
中找到is_accessible()
方法,接下来我们对该方法的校验进行分析:
linker_namespaces.cpp
...
bool android_namespace_t::is_accessible(const std::string& file) {
if (!is_isolated_) {
return true;
}
for (const auto& dir : ld_library_paths_) {
if (file_is_in_dir(file, dir)) {
return true;
}
}
for (const auto& dir : default_library_paths_) {
if (file_is_in_dir(file, dir)) {
return true;
}
}
for (const auto& dir : permitted_paths_) {
if (file_is_under_dir(file, dir)) {
return true;
}
}
return false;
}
...
由以上代码中,我们可以发现共有4个条件,只要满足了其中一个条件,即可让校验返回为true,或者增加一个我们自己的校验判断。
首先我们查看一下在这个函数中,使用到的变量和参数的意义:
file
这个是上面提到的realpath,是我们要加载的so文件的真实地址:system\lib\libXXwebview.so
ld_library_paths_
: 已加载的library路径default_library_paths_
默认的library路径permitted_paths_
被允许的library路径is_isolated_
这个变量表示调用者的命名空间是否和当前的命名空间具有关联。具体可以参考:Namespace based Dynamic Linking - Isolating Native Library of Application and System in Android我们回顾之前的异常log,可以发现各个变量的取值:
ld_library_paths="",
default_library_paths="/system/app/Browser/lib/arm",
permitted_paths="/data:/mnt/expand"
根据取值我们可以确定,只要这个file
的文件是在这些文件夹中即可。而由异常的log可知我们的目标so文件的真实path是在system\lib
目录下的,所以无法通过访问的验证。那么我们只要想办法,让我们目标的so文件真实的路径在 default_library_paths
中,或者在这三个变量中增加我们的system\lib
路径即可。
那么要在哪里增加这个路径呢?我们如果换一个也会出现这个问题的apk,我们会发现 ld_library_paths
还是空的,permitted_paths
还是取值 /data:/mnt/expand
。那这就意味着 /data:/mnt/expand
是一个常量字符串,我们在系统中尝试搜索这个字符串。
...
// (http://b/27588281) This is a workaround for apps using custom classloaders and calling
// System.load() with an absolute path which is outside of the classloader library search path.
// This list includes all directories app is allowed to access this way.
static constexpr const char* kWhitelistedDirectories = "/data:/mnt/expand";
...
从注释中我们可以发现,这是一个白名单的目录列表,在这个列表中的所有文件夹中,任何的app都可以访问的。我们app安装后,apk中的so文件会释放在/data
的子目录中,而/data
目录是一个白名单,这也就解释了为什么install的apk是可以正常加载我们自己的so,但是push或者固件预置的apk是不行加载的,因为固件预置的apk的so文件是在/system/lib
中,不再这个白名单中。那么我们是不是可以尝试将/system/lib
的路径加入这个变量中呢?答案是不行的,我们不能这么做。这么做确实可以解决我们的问题,但是这么做的风险是非常大的,而且这么做的话cts是过不了的。如果对这个修改想更具体的了解,可以查看google的:Bug 27588281
那么增加白名单的目录是不行的,那我们的问题应该如何解决呢?
native_loader.cpp
文件: // For vendor apks, give access to the vendor lib even though
// they are treated as unbundled; the libs and apks are still bundled
// together in the vendor partition.
#if defined(__LP64__)
std::string vendor_lib_path = "/vendor/lib64";
#else
std::string vendor_lib_path = "/vendor/lib";
#endif
library_path = library_path + ":" + vendor_lib_path.c_str();
permitted_path = permitted_path + ":" + vendor_lib_path.c_str();
...
以上的描述我们可以发现,如果我们的apk是放在/vendor/app
的目录下面,再将我们的so文件放在/vendor/lib
下,那么我们的apk也是可以正常加载到so的。
static constexpr const char kPublicNativeLibrariesSystemConfigPathFromRoot[] =
"/etc/public.libraries.txt";
static constexpr const char kPublicNativeLibrariesExtensionConfigPrefix[] = "public.libraries-";
static constexpr const size_t kPublicNativeLibrariesExtensionConfigPrefixLen =
sizeof(kPublicNativeLibrariesExtensionConfigPrefix) - 1;
static constexpr const char kPublicNativeLibrariesExtensionConfigSuffix[] = ".txt";
static constexpr const size_t kPublicNativeLibrariesExtensionConfigSuffixLen =
sizeof(kPublicNativeLibrariesExtensionConfigSuffix) - 1;
static constexpr const char kPublicNativeLibrariesVendorConfig[] =
"/vendor/etc/public.libraries.txt";
static constexpr const char kLlndkNativeLibrariesSystemConfigPathFromRoot[] =
"/etc/llndk.libraries.txt";
static constexpr const char kVndkspNativeLibrariesSystemConfigPathFromRoot[] =
"/etc/vndksp.libraries.txt";
void Initialize() {
// Once public namespace is initialized there is no
// point in running this code - it will have no effect
// on the current list of public libraries.
if (initialized_) {
return;
}
std::vector<std::string> sonames;
const char* android_root_env = getenv("ANDROID_ROOT");
std::string root_dir = android_root_env != nullptr ? android_root_env : "/system";
std::string public_native_libraries_system_config =
root_dir + kPublicNativeLibrariesSystemConfigPathFromRoot;
std::string llndk_native_libraries_system_config =
root_dir + kLlndkNativeLibrariesSystemConfigPathFromRoot;
std::string vndksp_native_libraries_system_config =
root_dir + kVndkspNativeLibrariesSystemConfigPathFromRoot;
std::string product_public_native_libraries_dir = "/product/etc";
std::string error_msg;
LOG_ALWAYS_FATAL_IF(
!ReadConfig(public_native_libraries_system_config, &sonames, always_true, &error_msg),
"Error reading public native library list from \"%s\": %s",
public_native_libraries_system_config.c_str(), error_msg.c_str());
// For debuggable platform builds use ANDROID_ADDITIONAL_PUBLIC_LIBRARIES environment
// variable to add libraries to the list. This is intended for platform tests only.
if (is_debuggable()) {
const char* additional_libs = getenv("ANDROID_ADDITIONAL_PUBLIC_LIBRARIES");
if (additional_libs != nullptr && additional_libs[0] != '\0') {
std::vector<std::string> additional_libs_vector = base::Split(additional_libs, ":");
std::copy(additional_libs_vector.begin(), additional_libs_vector.end(),
std::back_inserter(sonames));
}
}
// android_init_namespaces() expects all the public libraries
// to be loaded so that they can be found by soname alone.
//
// TODO(dimitry): this is a bit misleading since we do not know
// if the vendor public library is going to be opened from /vendor/lib
// we might as well end up loading them from /system/lib or /product/lib
// For now we rely on CTS test to catch things like this but
// it should probably be addressed in the future.
for (const auto& soname : sonames) {
LOG_ALWAYS_FATAL_IF(dlopen(soname.c_str(), RTLD_NOW | RTLD_NODELETE) == nullptr,
"Error preloading public library %s: %s", soname.c_str(), dlerror());
}
system_public_libraries_ = base::Join(sonames, ':');
// read /system/etc/public.libraries-.txt which contain partner defined
// system libs that are exposed to apps. The libs in the txt files must be
// named as lib..so.
sonames.clear();
ReadExtensionLibraries(base::Dirname(public_native_libraries_system_config).c_str(), &sonames);
oem_public_libraries_ = base::Join(sonames, ':');
// read /product/etc/public.libraries-.txt which contain partner defined
// product libs that are exposed to apps.
sonames.clear();
ReadExtensionLibraries(product_public_native_libraries_dir.c_str(), &sonames);
product_public_libraries_ = base::Join(sonames, ':');
// Insert VNDK version to llndk and vndksp config file names.
insert_vndk_version_str(&llndk_native_libraries_system_config);
insert_vndk_version_str(&vndksp_native_libraries_system_config);
sonames.clear();
ReadConfig(llndk_native_libraries_system_config, &sonames, always_true);
system_llndk_libraries_ = base::Join(sonames, ':');
sonames.clear();
ReadConfig(vndksp_native_libraries_system_config, &sonames, always_true);
system_vndksp_libraries_ = base::Join(sonames, ':');
sonames.clear();
// This file is optional, quietly ignore if the file does not exist.
ReadConfig(kPublicNativeLibrariesVendorConfig, &sonames, always_true, nullptr);
vendor_public_libraries_ = base::Join(sonames, ':');
}
在这段代码中,会出现几个文件的路径:
/system/etc/public.libraries.txt
/system/etc/llndk.libraries.txt
:不存在/system/etc/vndksp.libraries.txt
:不存在/vendor/etc/public.libraries.txt
我们可以打开手机查看,发现2、3和5的路径是不存在的,有的只有 1 和 4 的路径。
我们打开adb shell
在手机文件中查看 1 和 4 文件的具体内容,发现其实这里就是一些so文件名,这些so文件都是一些系统的so:
/system/etc/public.libraries.txt
/vendor/etc/public.libraries.txt
system
的文本中,手机无法开机。vender
的文本中,手机可以开机,并且在android8.0
中可以解决so链接的问题。由此我们可以发现,我们可以利用vender
的文本来进行so的白名单添加。
至于为什么可以在
vender
中验证通过,这其实这是一个bug,理论上我们作为system app
的形式存在于手机中,而非vender app
的形式存在手机中,校验的文件应该是system
的,而非vender
的。
Android 9.0
的时候,这个方法就失效了,添加在vender
文本中的白名单只会对vender app
才有效果。**但是我们的app是system的app,所以不能放进vender中,因为那样会降低我们app的权限范围,反而会引发出其他的问题。我们回顾之前的异常日志:
library "/system/app/Browser/lib/arm/libXXwebview.so" ("/system/lib/libXXwebview.so") needed or dlopened by "/system/lib/libnativeloader.so" is not accessible for the namespace: [name="classloader-namespace", ld_library_paths="", default_library_paths="/system/app/Browser/lib/arm", permitted_paths="/data:/mnt/expand"]
so的加载路径有2个:
"/system/app/Browser/lib/arm"
"/data:/mnt/expand"
如果是普通install
的apk,那么他的so文件会释放在/data
的目录,而系统预制的apk则会把apk中的so文件拷贝到system/lib中,这么做是属于系统编译的优化工作,让所有的系统apk共享相同的so文件,不需要每个apk都自己去加载重复的apk。但是这也会引发一些linker加载的权限问题。而系统内置apk的"/system/app/Browser/lib/arm"
中的so文件都并不是真实的so文件,而是so文件的快捷方式。也就是说,只要我们在编译的时候,将真实的so文件拷贝到"/system/app/Browser/lib/arm"
这个目录,理论上就可以解决该问题。
接着我将apk中真实的so文件上传到该目录,发现问题得到解决。
修改编译脚本为:
$(warning "start copy lib form $(LOCAL_PATH)/lib to $(LOCAL_MODULE_PATH)/$(LOCAL_MODULE)/")
$(shell mkdir -p $(LOCAL_MODULE_PATH)/$(LOCAL_MODULE)/lib)
$(shell cp -f -r $(LOCAL_PATH)/lib/ $(LOCAL_MODULE_PATH)/$(LOCAL_MODULE)/)
$(warning "end copy")
重新对系统进行编译,刷机后测试问题,已解决,且支持所有android系统版本。
最后我们对本文做一个回顾,先是通过log在源码中搜索相关的日志,然后找到相关性代码,接着走读代码,发现权限验证,然后尝试找到白名单,但是发现不能彻底解决问题,最后在将so文件传到lib文件夹,验证发现ok,修改编译脚本,解决问题。
在这个深入探索的过程中,真正得到的不是最终解决问题的这个方法,而是在走读代码的过程中我们对android的native linker有了更加深入的了解,并且明白了日后异常的问题的解决手法和方式是怎么样的。