【问题标题】:Debugging Objective C JNI code调试 Objective C JNI 代码
【发布时间】:2012-10-04 18:36:30
【问题描述】:

情况如下:

我在 Eclipse 中打开了一个客户的 java 项目。它使用由 Xcode Objective C 项目创建的 JNI 库。当我执行 Java 代码时,有什么好的方法可以让我从 Eclipse 中调试 C 代码吗?显然eclipse的默认调试器无法单步进入jni库文件,我们丢失了线程(线程在这里表示调查线程,而不是编程线程)。

感谢任何建议或输入,因为代码库足够大,遵循客户的代码将比其他选项快得多。

谢谢。

编辑:

需要注意的是,jni 库之所以用 Objective-C 编写,是因为它是与 Mac OSX 集成的。它使用 Cocoa 框架与 Apple 语音 api 集成。

【问题讨论】:

标签: objective-c xcode eclipse debugging java-native-interface


【解决方案1】:

我不确定我是否完全了解您的设置,以及您是否需要在 Eclipse 中完成此操作。无论如何,我对使用 JNI 和 Cocoa 库做一个小测试程序很感兴趣,只是为了尝试调试 obj-c/c 代码。

我成功地完成了这个设置并调试了代码。我将 IntelliJ 用于 Java,将 Xcode 用于 objc/c 部分,但在 eclipse 中执行 java 部分是不费吹灰之力的。

所以您应该能够准确地设置我的项目结构并开始调试。从那里您应该能够将这些知识应用到您自己的更复杂的代码中。

我就是这样开始的:

  • 通过选择 Cocoa 库在 Xcode 中创建一个新项目。

  • 将项目命名为 libnative 并将其设为动态类型。

  • 为您的新项目选择一个地方。我使用~/Development/ 并跳过Create local git... 部分。

  • 这将在您选择的文件夹中创建一个名为lib native.xcodeproj 的新项目。已自动创建两个文件:libnative.hlibnative.m

  • 首先您必须更改项目设置。

    • Packaging 部分中的 Executable Extension 必须从 dynlib 更改为 jnilib
    • 必须更新 Search Paths 部分中的 Framework Search Paths 以指向 JNI 框架:/System/Library/Frameworks/JavaVM.framework/Frameworks/JavaNativeFoundation.framework/

  • 现在是时候添加一些代码了。请注意,使用此设置您将不得不使用<JavaVM/jni.h>。将libnative.m 更新为如下代码:

//
//  libnative.m
//  libnative
//
//  Created by maba on 2012-10-09.
//  Copyright (c) 2012 maba. All rights reserved.
//

#import "libnative.h"
#include <JavaVM/jni.h>

@implementation libnative

@end

#ifdef __cplusplus
extern "C" {
#endif

#ifndef VEC_LEN
#define VEC_LEN(v) (sizeof(v)/sizeof(v[0]))
#endif/*VEC_LEN*/

static JavaVM *javaVM;

static void print();

static JNINativeMethod Main_methods[] =
{
    { "print", "()V", (void*)print },
};

static struct {
    const char      *class_name;
    JNINativeMethod *methods;
    int             num_methods;
} native_methods[] = {
    { "com/stackoverflow/Main", Main_methods, VEC_LEN(Main_methods) },
};

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) {
    JNIEnv *env = 0;
    jclass cls  = 0;
    jint   rs   = 0;

    if ((*jvm)->GetEnv(jvm, (void**)&env, JNI_VERSION_1_4)) {
        return JNI_ERR;
    }

    javaVM = jvm;

    for (unsigned int i = 0; i < VEC_LEN(native_methods); i++) {
        cls = (*env)->FindClass(env, native_methods[i].class_name);
        if (cls == NULL) {
            return JNI_ERR;
        }
        rs = (*env)->RegisterNatives(env, cls, native_methods[i].methods, native_methods[i].num_methods);
        assert(rs == JNI_OK);
    }

    return JNI_VERSION_1_4;
}

static void print(JNIEnv *env, jclass cls) {
    printf("Hello from C");
}

#ifdef __cplusplus
}
#endif
  • +B 构建代码。

  • 现在是时候创建 Java 代码了。我只是在包 com.stackoverflow 中创建了一个名为 Main 的类。

com.stackoverflow.Main.java

package com.stackoverflow;

/**
 * @author maba, 2012-10-09
 */
public class Main {

    static native void print();

    static {
        System.out.println(System.getProperty("java.library.path"));
        System.loadLibrary("native");
    }

    public static void main(String[] args) {
        System.out.println("Loading native");
        Main.print();
    }
}
  • Main.print(); 之前的行上设置断点。使用以下 JVM 选项启动调试器:

-Djava.library.path="/Users/maba/Library/Developer/Xcode/DerivedData/libnative-cquleqohzyhghnercyqdwpnznjdf/Build/Products/Debug/"

这一行有点长,而且是特定于用户的。您必须自己查找目录名称,但除了生成的 libnative-cquleqohzyhghnercyqdwpnznjdf 路径之外,它们或多或少与我的相同。

  • 程序应该正在运行并在断点处等待。是时候将 Xcode 调试器附加到正在运行的应用程序了。

  • 选择菜单Product -> Attach to Process &gt; 并在下拉列表的System 部分中指向正在运行的java 进程。如果有多个java 进程,那么它很可能是具有最高 PID 的进程,但并非总是如此。你一定要试试。

  • printf("Hello from C");行的c代码中创建断点。

  • 返回 Java IDE 并从停止的地方继续执行。

  • 回到Xcode,看看它在断点处等待!


正如我之前所说,这是一种非常简单的 obj-c/JNI 方法,您的项目可能非常大,但是通过这个小型测试项目,您至少可以看到它是如何工作的,然后继续您自己的项目设置。

【讨论】:

  • 请注意,调试是否在 Eclipse 中完成并不重要。问题是我们有一个正在调试的客户端项目(它与我们的软件集成),而可执行文件是一个 java 项目。然而,那个 Java 项目(在 Eclipse 中)使用了一个用 Objective C 编写的 JNI 库,我们在 Xcode 中有一个项目。这说明清楚了吗?我们不关心我们如何调试它,我们只需要能够单步调试 JNI 代码以及 cocoa 框架,看看它们是如何与 Mac OS X 的 Speech API 交互的
  • @thatidiotguy 因此,在这种情况下,您已经完成了所有设置。在eclipse中调试java部分,在调用native方法前设置断点。从 Xcode 附加到 java 进程。在 Xcode 的本地方法中设置断点。在 eclipse 中继续执行,你将在 Xcode 中的 JNI 断点处等待。
【解决方案2】:

您也许可以从终端使用gdb(或lldb)进行附加。如果带有本机代码的进程的启动是fork()/exec() 的结果——即如果你不能输入gdb /some/command/line——那么你可以使用--waitfor 选项(参见手册页)来等待劣质的推出。

加载符号会很棘手。


这是一个使用 cocoa 框架的 Mac OS X 项目。有没有影响 这个?

不应该。如果有的话,它会更容易,希望符号文件是可用的格式。关键通常是在 Java 和本机代码之间找到正确的突破点。

dylib 中的本机代码是加载到 JVM 中,还是您有自定义可执行文件在内部启动 JVM?

在任何情况下,您都需要将本机调试器附加到运行该本机代码的任何进程。可能在您适当地设置了基于 java 的调试会话之后。

【讨论】:

  • 这是一个使用 cocoa 框架的 Mac OS X 项目。这会影响这个吗?
  • 本机代码位于一个带有 jnilib 扩展名的文件中。它是通过System.loadLibrary("libName") 功能包含的,因此它被加载到JVM 中。
  • 对;因此,您需要将 gdb 附加到实际运行 jvm 的任何进程。然后在 gdb 中使用 add-symobl-file 为 jni 库添加符号。这应该可行,但可能会受到 JVM 中运行时异常的阻碍。
【解决方案3】:

过去在做 JNI 时,我构建了一个测试工具来促进应用程序的本机部分的开发 - 以及 JNI 代码 - 这是臭名昭著的容易搞砸的,避免了从双方同时调试的需要。

这是作为本机应用程序编写的,它以编程方式调用 JVM,而不是从 Java 应用程序开始,然后尝试附加到 JVM。

当然,您可以在 Xcode 中启动它并对其进行调试——这对于使用 CDT 的 Eclipse 来说是一种无限可取的体验。

这种安排的 Java 方面通常非常简单且不折不扣 - 基本上是一种从应用程序的本机部分调用的方法,然后通过 JNI 对本机部分进行一次或多次调用。

【讨论】:

  • 您能否提供有关如何设置环境以执行 java 代码(因为它进行 JNI 调用)但调试 c 代码的详细信息?
  • 我在以前的工作中做过这个,所以不再可以访问源代码来准确地告诉你我是如何做到的 - 但是,这个Apple Technote 是我作为我的起点的。请参阅从本机代码调用 Java 虚拟机部分。然后我使用从 AppKit 调用 AWT/Swing 部分中描述的技术来调用 JVM。
【解决方案4】:

这是我在 Windows 下调试 JNI (C/C++) 所遵循的步骤,我认为 ObjectiveC 也需要相同的步骤。对于 Linux,它非常相似(将 ; 替换为 :,将 %XXX% 替换为 ${XXX}...)。

  1. 创建一个名为 MyDebug.gdbinit 的文件
  2. 将这 4 行添加进去:

    set args -classpath .;xxxx.jar;yyy.jar path.to.your.Main

    显示参数

    运行

    bt

  3. 启动 gdb 和 Java:gdb "%JAVA_HOME%\bin\java"

  4. 使用 Java 层的 GUI 重现错误
  5. 如果你想逐步执行你的 JNI 代码,gdb 允许你放置一些断点

【讨论】:

    猜你喜欢
    • 2011-02-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-02
    • 2011-09-07
    • 2011-09-11
    • 1970-01-01
    相关资源
    最近更新 更多