调试Objective C JNI代码

5
这里是情况说明:
我在eclipse中打开了一个客户的java项目。它使用由Xcode Objective C项目创建的JNI库。有没有好的办法让我在执行Java代码时从eclipse调试C代码?显然,eclipse的默认调试器无法进入jni库文件,我们失去了线索(线索指的是调查线索,而不是编程线索)。
任何建议或意见都将受到赞赏,因为代码库足够大,按照客户的代码进行跟踪将比其他选项快得多。
谢谢。
编辑:
应该指出的是,jni库编写为Objective-C的原因是因为它正在与Mac OSX集成。它正在使用Cocoa框架与Apple语音API集成。

这个问题可能会提供一些见解,它是关于在Linux上使用gdb,但很可能也适用于Objective-C:https://dev59.com/questions/Hmox5IYBdhLWcg3wWzJ7 - Denis Tulskiy
如果运气好的话,你甚至可以连接Xcode来进行远程调试:https://dev59.com/x0XRa4cB1Zd3GeqPrnDt - Denis Tulskiy
4个回答

5
我不确定我是否完全理解了您的设置,以及您是否需要从eclipse完成此操作。无论如何,我有兴趣使用JNI和Cocoa库编写一个小测试程序,仅仅是为了尝试调试obj-c/c代码。
我成功地完成了这个设置,并且也成功地调试了代码。我使用IntelliJ进行Java开发,使用Xcode进行objc/c部分开发,但在eclipse中进行Java部分开发很简单。
因此,您应该能够完全按照我的项目结构进行设置,并开始调试。从那里,您应该能够将这些知识应用到您自己更复杂的代码中。
这就是我开始的方式:
  • 通过选择Cocoa Library在Xcode中创建一个新项目。

Cocoa Library

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

Choose Options

  • 选择一个地方作为你的新项目。我使用~/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/

enter image description here

现在是添加代码的时候了。请注意,使用此设置需要使用<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 >并指向下拉列表中System部分中运行的java进程。如果有多个java进程,则最可能是具有最高PID的那个,但不总是。你需要尝试一下。

  • 在C代码中的printf("Hello from C");行上创建一个断点。

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

  • 返回Xcode并查看它正在等待断点!

At Breakpoint


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


只是让您知道,无论调试是否在Eclipse中完成都没有关系。问题在于我们有一个客户项目正在进行调试(它与我们的软件集成),可执行文件是一个Java项目。然而,该Java项目(在Eclipse中)使用了一个用Objective C编写的JNI库,我们在Xcode中有一个项目。这样清楚了吗?我们不关心如何调试它,我们只需要能够逐步查看JNI代码以及Cocoa框架,以查看它们如何与Mac OS X的语音API交互。 - thatidiotguy
@thatidiotguy,在这种情况下,你已经为此做好了所有的设置。在Eclipse中调试Java部分,并在调用本地方法之前设置断点。从Xcode附加到Java进程。在Xcode中设置本地方法的断点。在Eclipse中继续执行,你将在Xcode的JNI断点处等待。 - maba
嗯,是的,这个答案很古老,但现在我需要这样做来调试一个被Java应用程序加载的.dylib文件。(顺便说一下,我正在使用Panama来进行GLE)所以我在Java中,在第一次调用库之前设置了一个断点。 (库成功加载)在Xcode中尝试附加到Java时,我得到了以下错误信息:附加失败(不允许附加到进程。当附加失败时,请查看控制台消息(Console.app),在debugserver条目附近。拒绝附加权限的子系统可能已经记录了有关为什么被拒绝的信息。) - peterk

1

你可以从终端使用gdb(或lldb)进行附加。如果启动本地代码的进程是通过fork()/exec()实现的——即如果您无法键入gdb /some/command/line——那么您可能可以使用--waitfor选项(请参阅man页面)等待下属的启动。

加载符号将会很棘手。


这是一个使用Cocoa框架的Mac OS X项目。这会影响到它吗?
不应该有影响。如果有的话,希望符号文件的格式可用,这将使它更容易。关键通常是找到在Java和本地代码之间的边界处正确的断点位置。
本地代码是否在一个dylib中加载到JVM中,或者您是否有一个自定义可执行文件,在其中内部启动JVM?
无论如何,您需要将本地调试器附加到运行本地代码的任何进程上。可能是在适当设置Java调试会话之后。

这是一个使用Cocoa框架的Mac OS X项目。这会有影响吗? - thatidiotguy
本地代码的文件扩展名为jnilib。通过System.loadLibrary("libName")功能进行包含,因此它在JVM中加载。 - thatidiotguy
好的,所以您需要使用gdb附加到实际运行jvm的进程。然后在gdb中使用add-symbol-file添加jni库的符号。这应该可以工作,但可能会受到JVM中运行时奇怪问题的阻碍。 - bbum

1
过去在进行JNI时,我建立了一个测试工具来促进应用程序的本地部分和JNI代码的开发 - 这是非常容易出错的,避免了同时从两个方面进行调试的需要。
这是作为一个本地应用程序编写的,它以编程方式调用JVM,而不是从Java应用程序开始,然后尝试附加到JVM。
当然,您可以在Xcode中启动并调试它 - 这比使用CDT的Eclipse更可取。
这种安排的Java端通常非常简单和非争议性 - 基本上是从应用程序的本地部分调用的方法,然后通过JNI回调一个或多个本地部分。

你能详细说明如何设置环境,以便执行Java代码(因为它会进行JNI调用),但同时调试C代码吗? - thatidiotguy
我在以前的工作中做过这个,所以不再有访问源代码的权限来告诉你我是如何做到的 - 然而,这个苹果技术笔记是我用作起点的。请参阅从本地代码调用Java虚拟机部分。然后我使用了第从AppKit调用AWT/Swing部分描述的技术来调用JVM。 - marko

0

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

  1. 创建名为MyDebug.gdbinit的文件
  2. 将以下4行添加到其中:

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

    show args

    run

    bt

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

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接