UnsatisfiedLinkError: dlopen failed: so: has text relocations

使用别人的SDK,(我调用别人的jar,别人的jar再调用他自己的的so)调用的时候发现提示这个弹框,表现是如果编译版本在27则app调用到别人的so的时候直接闪退,把app版本降低为22以后则每次开机运行都会弹框提示上述那段话,经过搜索一番说是:

若使用的so库包含text relocation,

  • 在Android 5.1会报出警告,但仍允许执行。
  • 在Android 6.0及更高版本,会停止运行(增加runtime开销影响性能, 并引入安全问题)。
  • 见链接:https://www.jianshu.com/p/fd938a51b01f

Android 6.0 change

On previous versions of Android, if your app requested the system to load a shared library with text relocations, the system displayed a warning but still allowed the library to be loaded. Beginning in this release, the system rejects this library if your app's target SDK version is 23 or higher. To help you detect if a library failed to load, your app should log the dlopen(3) failure, and include the problem description text that the dlerror(3) call returns. To learn more about handling text relocations, see this guide.

 知道了这个问题是因为系统版本在6.0以后就会对so使用进行限制,但是因为设备系统版本无法降低,那只能通过修改so来解决。

但是问题是so的方法是被别人的jar调用的,而且没有源码,无法修改。

通过一番分析,发现别人的业务全部都是在jar中实现的,而真正在so中完成的功能只有打开关闭串口等功能,见下:

package android_serialport_api;

import android.util.Log;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class SerialPort {
    private static final String TAG = "SerialPort";
    private FileDescriptor mFd;
    private FileInputStream mFileInputStream;
    private FileOutputStream mFileOutputStream;

    static {
        System.loadLibrary("serial_port");
    }

    public SerialPort(File device, int baudrate, int dataBits, int stopBits, int parity, int flags) throws SecurityException, IOException {
        if (!device.canRead() || !device.canWrite()) {
            try {
                Process su = Runtime.getRuntime().exec("/system/bin/su");
                String cmd = "chmod 666 " + device.getAbsolutePath() + "\n" + "exit\n";
                su.getOutputStream().write(cmd.getBytes());
                if (su.waitFor() != 0 || !device.canRead() || !device.canWrite()) {
                    throw new SecurityException();
                }
            } catch (Exception var9) {
                var9.printStackTrace();
                throw new SecurityException();
            }
        }

        this.mFd = open(device.getAbsolutePath(), baudrate, flags);
        if (this.mFd == null) {
            Log.e("SerialPort", "native open returns null");
            throw new IOException();
        } else {
            this.mFileInputStream = new FileInputStream(this.mFd);
            this.mFileOutputStream = new FileOutputStream(this.mFd);
        }
    }

    public InputStream getInputStream() {
        return this.mFileInputStream;
    }

    public OutputStream getOutputStream() {
        return this.mFileOutputStream;
    }

    private static native FileDescriptor open(String var0, int var1, int var2);

    public native void close();
}

从代码分析,最终jar中调用都是调用到了这里,也就说调用到的so的函数只有两个方法:

private static native FileDescriptor open(String var0, int var1, int var2);

public native void close();

 那么可以通过自己编写一个so,实现这俩方法,然后打包成so替换原始的so即可,

于是便自己用NDK编写了一个DEMO,只实现这俩方法,见下是SerialPort.h文件的声明:

/* DO NOT EDIT THIS FILE - it is machine generated */
/* Header for class android_serialport_api_SerialPort */

#ifndef _Included_android_serialport_api_SerialPort
#define _Included_android_serialport_api_SerialPort

#include "../../../../../../sdk/ndk/21.1.6352462/toolchains/llvm/prebuilt/windows-x86_64/sysroot/usr/include/jni.h"

#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     android_serialport_api_SerialPort
 * Method:    open
 * Signature: (Ljava/lang/String;II)Ljava/io/FileDescriptor;
 */
extern "C" JNIEXPORT jobject JNICALL Java_android_serialport_api_SerialPort_open
        (JNIEnv *, jclass clazz, jstring, jint, jint);

/*
 * Class:     android_serialport_api_SerialPort
 * Method:    close
 * Signature: ()V
 */
extern "C" JNIEXPORT void JNICALL Java_android_serialport_api_SerialPort_close
        (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

下面是SerialPort.cpp

/*
 * Copyright 2009-2011 Cedric Priscal
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <termios.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <jni.h>

#include "SerialPort.h"

#include <android/log.h>
static const char *TAG="serial_port";
#define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO,  TAG, fmt, ##args)
#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, TAG, fmt, ##args)
#define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, TAG, fmt, ##args)

static speed_t getBaudrate(jint baudrate)
{
    switch(baudrate) {
        case 0: return B0;
        case 50: return B50;
        case 75: return B75;
        case 110: return B110;
        case 134: return B134;
        case 150: return B150;
        case 200: return B200;
        case 300: return B300;
        case 600: return B600;
        case 1200: return B1200;
        case 1800: return B1800;
        case 2400: return B2400;
        case 4800: return B4800;
        case 9600: return B9600;
        case 19200: return B19200;
        case 38400: return B38400;
        case 57600: return B57600;
        case 115200: return B115200;
        case 230400: return B230400;
        case 460800: return B460800;
        case 500000: return B500000;
        case 576000: return B576000;
        case 921600: return B921600;
        case 1000000: return B1000000;
        case 1152000: return B1152000;
        case 1500000: return B1500000;
        case 2000000: return B2000000;
        case 2500000: return B2500000;
        case 3000000: return B3000000;
        case 3500000: return B3500000;
        case 4000000: return B4000000;
        default: return -1;
    }
}

/*
 * Class:     android_serialport_SerialPort
 * Method:    open
 * Signature: (Ljava/lang/String;II)Ljava/io/FileDescriptor;
 */
extern "C" JNIEXPORT jobject JNICALL Java_android_serialport_api_SerialPort_open
        (JNIEnv *env, jclass obj, jstring path, jint baudrate, jint flags)
{
    int fd;
    speed_t speed;
    jobject mFileDescriptor;

    /* Check arguments */
    {
        speed = getBaudrate(baudrate);
        if (speed == -1) {
            /* TODO: throw an exception */
            LOGE("Invalid baudrate");
            return NULL;
        }
    }

    /* Opening device */
    {
        jboolean iscopy;
        const char *path_utf = (*env).GetStringUTFChars(path, &iscopy);
        LOGD("Opening serial port %s with flags 0x%x", path_utf, O_RDWR | flags);
        fd = open(path_utf, O_RDWR | flags);
        LOGD("open() fd = %d", fd);
        (*env).ReleaseStringUTFChars( path, path_utf);
        if (fd == -1)
        {
            /* Throw an exception */
            LOGE("Cannot open port");
            /* TODO: throw an exception */
            return NULL;
        }
    }

    /* Configure device */
    {
        struct termios cfg;
        LOGD("Configuring serial port");
        if (tcgetattr(fd, &cfg))
        {
            LOGE("tcgetattr() failed");
            close(fd);
            /* TODO: throw an exception */
            return NULL;
        }

        cfmakeraw(&cfg);
        cfsetispeed(&cfg, speed);
        cfsetospeed(&cfg, speed);

        if (tcsetattr(fd, TCSANOW, &cfg))
        {
            LOGE("tcsetattr() failed");
            close(fd);
            /* TODO: throw an exception */
            return NULL;
        }
    }

    /* Create a corresponding file descriptor */
    {
        jclass cFileDescriptor = (*env).FindClass( "java/io/FileDescriptor");
        jmethodID iFileDescriptor = (*env).GetMethodID(cFileDescriptor, "<init>", "()V");
        jfieldID descriptorID = (*env).GetFieldID(cFileDescriptor, "descriptor", "I");
        mFileDescriptor = (*env).NewObject( cFileDescriptor, iFileDescriptor);
        (*env).SetIntField( mFileDescriptor, descriptorID, (jint)fd);
    }

    return mFileDescriptor;
}

/*
 * Class:     cedric_serial_SerialPort
 * Method:    close
 * Signature: ()V
 */
extern "C" JNIEXPORT void JNICALL Java_android_serialport_api_SerialPort_close
        (JNIEnv *env, jobject thiz)
{
    jclass SerialPortClass = (*env).GetObjectClass( thiz);
    jclass FileDescriptorClass = (*env).FindClass( "java/io/FileDescriptor");

    jfieldID mFdID = (*env).GetFieldID(SerialPortClass, "mFd", "Ljava/io/FileDescriptor;");
    jfieldID descriptorID = (*env).GetFieldID( FileDescriptorClass, "descriptor", "I");

    jobject mFd = (*env).GetObjectField( thiz, mFdID);
    jint descriptor = (*env).GetIntField( mFd, descriptorID);

    LOGD("close(fd = %d)", descriptor);
    close(descriptor);
}

写好后编译,打包成so,用自己的demo进行调用:

package android.serialport.api;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import com.test.cplusplustest.R;

import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

import androidx.appcompat.app.AppCompatActivity;

public class SerialPort extends AppCompatActivity {


	private FileDescriptor mFd;
	private FileInputStream mFileInputStream;
	private FileOutputStream mFileOutputStream;

	private Button button;
	private Button button2;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		// Example of a call to a native method
		TextView tv = findViewById(R.id.sample_text);
		tv.setText("23456");
		button = findViewById(R.id.button);
		button.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				if (mFileOutputStream != null){
					try {
						byte[] bb = {0x02, 0x03, 0x06, 0x30, 0x41, (byte) 0xB3};
						mFileOutputStream.write(bb);
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			}
		});
		button2 = findViewById(R.id.button2);

		mFd = open("/dev/ttyS1",9600,0);
		mFileOutputStream  = new FileOutputStream(this.mFd);
		mFileInputStream = new FileInputStream(this.mFd);
	}


	static {
		System.loadLibrary("serialport");
	}

	private   static native FileDescriptor open(String name, int baudRate, int flags);
	public  native void close();

}

通过上述调用,发现可以打开串口,并发送数据成功,设备也正常反应,那么说明so修改成功。

demo目录结构图见下:

 

于是替换原始的SDK中的so,发现遇到问题:

java.lang.UnsatisfiedLinkError: No implementation found for java.io.FileDescriptor android_serialport_api.SerialPort.open(java.lang.String, int, int) 

原来的SDK中的jar调用的packageName是

package android_serialport_api;

而我demo测试的时候用的packageName是:

package android.serialport.api;

于是我把packageName改为带下划线的,但是发现还是找不到,提示错误都是:

java.lang.UnsatisfiedLinkError: No implementation found for java.io.FileDescriptor android_serialport_api.SerialPort.open(java.lang.String, int, int)

就卡在这里了。因为so已经打包验证,但是放进去无法正常使用,后面经过交流,发现可以修改jar中的包名,于是下载jarjar.jar这个工具,具体使用见链接:

https://blog.csdn.net/st526403649/article/details/69266907

按照描述,修改了jar中的包名,再次替换jar和so,运行正常。

rule pattern result
zap pattern
rule android_serialport_api.** android.serialport.api.@1

在此做一记录。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
android 串口驱动源代码 package android.serialport; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import android.util.Log; public class SerialPort { private static final String TAG = "SerialPort"; /* * Do not remove or rename the field mFd: it is used by native method close(); */ private FileDescriptor mFd; private FileInputStream mFileInputStream; private FileOutputStream mFileOutputStream; public SerialPort(File device, int baudrate) throws SecurityException, IOException { /* Check access permission */ if (!device.canRead() || !device.canWrite()) { try { /* Missing read/write permission, trying to chmod the file */ Process su; su = Runtime.getRuntime().exec("/system/bin/su"); /*String cmd = "chmod 777 " + device.getAbsolutePath() + "\n" + "exit\n";*/ String cmd = "chmod 777 /dev/s3c_serial0" + "\n" + "exit\n"; su.getOutputStream().write(cmd.getBytes()); if ((su.waitFor() != 0) || !device.canRead() || !device.canWrite()) { throw new SecurityException(); } } catch (Exception e) { e.printStackTrace(); throw new SecurityException(); } } mFd = open(device.getAbsolutePath(), baudrate); if (mFd == null) { Log.e(TAG, "native open returns null"); throw new IOException(); } mFileInputStream = new FileInputStream(mFd); mFileOutputStream = new FileOutputStream(mFd); } // Getters and setters public InputStream getInputStream() { return mFileInputStream; } public OutputStream getOutputStream() { return mFileOutputStream; } // JNI private native static FileDescriptor open(String path, int baudrate); public native void close(); static { System.loadLibrary("serial_port"); } }

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值