Here is the situation:
I have a client's java project open in eclipse. It uses a JNI library created by an Xcode Objective C project. Is there any good way for me to debug the C code from eclipse when I execute the Java code? Obviously eclipse's default debugger cannot step into the jni library file and we lose the thread (thread meaning investigative thread here, not programming thread).
Any advice or input is appreciated as the code base is large enough that following the client's code will be radically faster than other options.
Thanks.
EDIT:
It should be noted that the reason that the jni library is written in Objective-C is because it is integrating with Mac OSX. It is using the Cocoa framework to integrate with the Apple speech api.
I am not sure that I have fully understood your setup and if you require this to be done from eclipse or not. Anyhow I was interested in doing a little test program using JNI and a Cocoa library doing nothing just to try the debugging of the obj-c/c code.
I succeeded to do this setup and also to debug the code. I use IntelliJ for Java and Xcode for the objc/c part but doing the java part in eclipse is a no-brainer.
So you should be able to set up exactly my project structure and get going with the debugging. And from there you should be able to apply this knowledge to your own more complex code.
This is how I started off:
Create a new project in Xcode by choosing Cocoa Library.
Name the project libnative and make it of Type Dynamic.
Choose a place for your new project. I use ~/Development/ and skip the Create local git... part.
This will create a new project called lib native.xcodeproj in your selected folder. Two files have been automatically created: libnative.h and libnative.m.
First you must change the Project Settings.
Executable Extension in the Packaging section must be changed from dynlib to jnilib.
Framework Search Paths in the Search Paths section must be updated to point to the JNI framework: /System/Library/Frameworks/JavaVM.framework/Frameworks/JavaNativeFoundation.framework/
Now its time to add some code. Be aware that with this setup you will have to use <JavaVM/jni.h>. Update the libnative.m to look like the following code:
//
// 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
Build the code by pressing ⌘+B.
And now it is time to create the Java code. I simply created a class called Main in package com.stackoverflow.
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();
}
}
Set a breakpoint on the line before Main.print();. Start the debugger with the following JVM option:
-Djava.library.path="/Users/maba/Library/Developer/Xcode/DerivedData/libnative-cquleqohzyhghnercyqdwpnznjdf/Build/Products/Debug/"
This line is a little long and also user specific. You will have to look for yourself what the directory names are but they will be more or less the same as mine except for the generated libnative-cquleqohzyhghnercyqdwpnznjdf path.
The program should be running and waiting at the breakpoint. Time to attach the Xcode debugger to the running application.
Choose menu Product -> Attach to Process > and point to the running java process in the System part of the drop down. If there are several java processes then it is most likely the one with the highest PID but not always. You'll have to try.
Create a breakpoint in the c code on the line printf("Hello from C");.
Go back to the Java IDE and continue the execution from where it was halting.
Go back to Xcode and see that it is waiting at the breakpoint!
As I stated earlier this is a very simple approach to the obj-c/JNI and your project is probably quite large but with this small test project you can at least see how it works and then continue to your own project setup.
You might be able to attach with gdb (or lldb) from the Terminal. If the launching of the process w/the native code is the result of a fork()/exec() -- i.e. if you can't type gdb /some/command/line -- then you can likely use the --waitfor option (see the man page) to wait for the launch of the inferior.
Loading symbols will be tricky.
This is a Mac OS X project using the cocoa framework. Does that affect
this?
It shouldn't. If anything, it'll make it easier in that, hopefully, the symbol files are of a usable format. The key is typically finding the right spot to break at the boundary between java and native code.
Is the native code in a dylib that is loaded into the JVM or do you have a custom executable that fires up the JVM internally?
In any case, you need to attach the native debugger to whatever process is running that native code. Probably after you've set up the java based debugging session appropriately.
In the past when doing JNI I have built a test-harness to facilitate the development of the native part of the application - and JNI code - which is notorious easy to screw up, avoiding the need to debug simultaneously from both sides.
This was written as a native application that invokes the JVM programmatically rather than starting with a Java application and then attempting to attach to JVM.
You can of course, start this and debug it in Xcode - which is an infinitely preferable experience to Eclipse with CDT.
The Java side of this arrangement is usually pretty simple and non-contriverial - basically a method which is called from the native part of the app that then makes one or more calls back into the native portion through JNI.
Here are the steps I follow to debug JNI (C/C++) under Windows, I presume ObjectiveC need the same. For Linux it's very similar (replace ; by :, %XXX% by ${XXX}...).
Create a file named MyDebug.gdbinit
Add these 4 lines into it:
set args -classpath .;xxxx.jar;yyy.jar path.to.your.Main
show args
run
bt
launch gdb and Java: gdb "%JAVA_HOME%\bin\java"
Use the GUI of the Java layer to reproduce the error
If you want to execute step by step your JNI code, gdb allows you to put some breakpoints
Related
I'm following the book of Programming in Objective-C by Stephen G. Kochan. I was trying the code and to improve the class example by myself. I opened a project in Mac OS X / Applications / Command Line Tool and the program executes successfully.
When I opened the project as IOS / Framework & Library / Cocoa Touch Static Library, XCode separates class and implementation files normally. When I try to compile, XCode says it has built successfully but there is no output in the console.
I just followed the book and I am sure there is nothing wrong about Class or the implementation files. "NSLog(#""); files stays in there". According to the book, files are separated by 3:
Interface Part (class part)
Implementation Part (Which instances located in)
int main (int argc, char *argv[]) part.
But when I open the project as cocoa-static library, I get only 1 *.m file. I cannot add any additional *.m file with add -> new file.
My question is, is there any relative problem with my file structure that I am working on? Should I need also separate implementation part and the main part?
I would really appreciate if someone could help with this probelem. I really got stuck and having struggling to proceed next step of the book because I can not try code examples anymore...
Sounds like you're trying to run a static library project. You can't do this - you need to make an app that uses your static library to be able to run at and see the output.
I'm going to create a mail plugin for the OS X Mail.app application for some additional features.
I have no idea where to start as there is no official documentation for plugins.
Can anyone please help me, how can I start the project.
Is there any initial link or tutorial, please suggest?
As noted, writing Apple Mail plugins is not straightforward, since it only has a private plugin API, which is entirely undocumented and can change with any new version of Mail.app. The best code example is GPGMail, which is open source & still active (already working on Yosemite support). Here is what I successfully did to get started (will put it up on github once finished):
How to build a minimal Apple Mail plugin (as of Mavericks & Xcode 6.0.1)
you need to create an OSX "Bundle" project in XCode
wrapper extension is mailbundle (under Packaging in the project Build settings)
a bundle needs to be stored under ~/Library/Mail/Bundles (as Build Phase add a Copy Files action with that as absolute path destination and the *.mailbundle from your build/ folder as item to copy)
for development, I have set up /Applications/Mail.app as executable in my run scheme, so that Run in XCode will build it, copy the bundle and start mail; note that at this point you'll get an error from Mail that your plugin cannot be started and was disabled
you need to provide a list of SupportedPluginCompatibilityUUIDs in the Info.plist, I stole it from GPGMail, these change with new Mail/OSX versions
use class-dump to generate the header files from Mail.app's private API
starting point is MVMailBundle, which you have to inherit from and which has a registerBundle method to hook you in
I extracted that from the huge generated header file in a small MVMailBundle.h header to include where needed (as done by GPGMail)
create a new class MyMailBundle, inheriting from NSObject
it needs an initialize method
and set it as "Principle class" in the Info.plist so that it gets run when the bundle is loaded by Mail.app
#import <Cocoa/Cocoa.h>
#interface MyMailBundle : NSObject
+ (void)initialize;
#end
initialize implementation: previously, you could use the simple way and directly inherit as done in Letterbox, however, since 64-bit runtimes of Objective-C you have to use the dynamic way as done by GPGMail:
using NSClassFromString to dynamically get the MVMailBundle class
and class_setSuperclass from <objc/runtime.h> to have your own class inherit from it
and then call registerBundle on it casted as MVMailBundle (requires include of MVMailBundle.h)
#import <objc/runtime.h>
#import "MVMailBundle.h"
#import "MyMailBundle.h"
#implementation MyMailBundle
+ (void)initialize
{
NSLog(#"Loading MyMail plugin...");
// since 64-bit objective-c runtimes, you apparently can't load
// symbols directly (i.e. through class inheritance) and have to
// resort to NSClassFromString
Class mvMailBundleClass = NSClassFromString(#"MVMailBundle");
// If this class is not available that means Mail.app
// doesn't allow plugins anymore or has changed the API
if (!mvMailBundleClass)
return;
// dynamically change super class hierarchy
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated"
class_setSuperclass([self class], mvMailBundleClass);
#pragma GCC diagnostic pop
// register our plugin bundle in mail
[[((MyMailBundle *)self) class] registerBundle];
NSLog(#"Done registering MyMail plugin.");
}
#end
add some NSLog logging calls to verify the right thing is happening, they'll be visible in XCode's console when running/debugging Mail.app from within XCode or alternatively in the system logs of Console.app
This should successfully run the plugin in Mail with no error!
The next steps involve crazy things like MethodSwizzling and ClassPosing to modify Mail's behavior, where GPGMail can be a helpful example. (Haven't been there myself yet)
For reference, here are some of the resources that helped me:
GPGMail
Adam Nash: Getting Ready to Write an Apple Mail.app Plug-in for Mac OS X - some good links, but apparently he never finished the project, so no code
James R. Eagan: Demystifying Mail.app Plugins on Leopard - using PyObjC to write a plugin in Python, explains the basic mechansims, very useful
Aaron Harnly: Mail Plugin Template - for XCode 2 I think, unfortunately the template (download a zip) doesn't work as template in Xcode anymore, but the code is still useful to look at
Aaron Harnly: Letterbox sources - from the same guy, but also from 2007, very outdated; contains a readme from the template, though it doesn't really help if you can't use the template.
There is no official supported way to build such a tool - you need to start trying to hook in to Mail.app without any official support.
If you want to persist on this sort of thing, then you'll need to understand how Mail.app internals work, which is a bunch of using the debugger and class dump to inspect libraries in other apps:
https://github.com/nygard/class-dump
You'll probably also want a way to inject code into other applications, for example:
https://github.com/rentzsch/mach_inject
And every time Apple update Mail.app you'll potentially need to redo everything :)
I made a xcode project where i did some security stuff and they asked me to obfuscate the method names
like so
#define specialMethod a9328238
+(void) specialMethod
{
// do security stuff
}
i made a .framework library from the project ( project A ) and included it into another project ( project B ).
but when i run (project B) with a Release build configuration it always crashes like so.
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[SecurityClass a9328238]: unrecognized selector sent to class 0x337cc4'
so it crashes when it tries to acces the method.
But when i run (project B) it with a Debug build configuration it runs smooth
(i have kept all my build configuration settings as default)
Where have you placed the #define for obfuscation ? Is it in the header file (.h) or in the implementation file (.m) of the framework ?
For the obfuscation to be effective, it must be placed in a file that is both included by the implementation and the caller.
You can also check that the pre-processing is ok by inspecting the pre-processed file. Select the implementation file and go to the menu Product > Generate Output > Generate Preprocessed File (you can select the configuration at the bottom of the screen).
My hunch is the #define location/visibility as well.
But you may want to consider this from another angle. You could change:
#define specialMethod a9328238
+(void) specialMethod
{
// do security stuff
}
to:
#interface SecurityClass : NSObject
// private obfuscated interface:
+ (void)a9328238;
// {
// do security stuff in a9328238's definition
// }
#end
// here is the public interface:
static inline void SecurityClass_LogIn() {
[SecurityClass a9328238];
}
dropping #define altogether.
In use:
SecurityClass_LogIn();
…
Since this is a class method, you could write an obfuscated function wrapped in a human readable inline instead. A well crafted C implementation will be much more difficult to pick apart than objc.
A more complete example would help us narrow down the possibilities.
Also verify there are no warnings -- the compiler may warn you if you have called an undeclared selector. It's possible that the method is called where the #define is not visible in other cases.
It seems that the executable which imports the obfuscated framework tries to access the non-obfuscated methods.
You should check the symbols in the framework. Use nm on the static library in the framework to see the exported symbols (marked with a 't'). Make sure the symbols are obfuscated.
If you've wrapped everything into a framework, have you made sure that the appropriate headers are exposed outside of the framework? Headers inside a framework aren't exposed the same way as normal files are. Go to your Project->Build Phases, in the bottom right you should see "Add Copy Headers". This will add a new section in your build phases. Inside this section, click the "+" and the headers that define your method names.
Code:
struct IRenderingEngine {
virtual void Initialize(int width, int height) = 0;
virtual void Render() const = 0;
virtual void UpdateAnimation(float timeStep) = 0;
virtual void OnRotate(DeviceOrientation newOrientation) = 0;
virtual ~IRenderingEngine() {}
};
Learning opengles from a book for 3d iphone programming and it uses this example code but the book is targeted for xcode 3.x
Somehow I feel like its something with xcode 4....
EDIT:
Heres the actual error:
/Users/Dan/Documents/opengles/Hello Arrow/Hello Arrow/IRenderingEngine.hpp:27:2: error: unknown type name 'virtual' [1]
And that legitamtely is all that it takes to fail to compile, absolutely no other files. (Yes I've tried compiling with literally a main.m and this hpp file)
It is recognizing the hpp file as a cpp header file though, if I try to add it to the compiled files it says that "no rule to process file '$(PROJECT_DIR)/Hello Arrow/IRenderingEngine.hpp' of type sourcecode.cpp.h for architecture i386" so I really have no idea what is going on
Note that I compiled with main.m meaning I compiled another Cocoa/Foundation based application
I tried compiling for a c++ application and everything worked out just fine....
Similarly compiling with a main.mm test file worked fine too
heres the actual project, lemme know how insane I really am:
[Removed considering I lost the file]
Please rename the main.m to main.mm. This worked for me.
If you're using Xcode 4, try changing the name of file "AppDelegate.m" to "AppDelegate.mm". It works for me.
Changing the name of file "AppDelegate.m" to "AppDelegate.mm". It's correct!
I moved the #import "IRenderingEngine.hpp" line from the GLView.h file to the GLView.mm - this prevented it from being imported into the main.m and HelloArrowAppDelegate.m files when they were compiled - and restricted the import into the .mm file, that could handle the C++.
I also had to make a couple of other fixes for bugs I'd introduced when typing in the code - so apologies if that wasn't the only thing that needed to be done, but it might help those with similar problems!
if you call C++ files ( even if you only import them ) you need to change the .m file that call's it to .mm
This is just a stupid guess since I've never tried compiling something with the word virtual in a C compiler... but is there any chance that you were trying to compile this C++ code as C code? That's the only reason I can think of that a compiler wouldn't understand the keyword virtual.
The header <stdlib.h> is not the right one to use in a C++ program. When I replaced it with the c++ version of C's stdio library <cstdlib> then your code compiled for me.
I want to run tests in Xcode 4 using OCUnit without launching the simulator. Please, don't try and convince me I am doing unit testing wrong or anything like that. I like to do TDD the traditional way: write the API for the class in the tests, then make the class pass the tests. I will write separate tests that are end-to-end that run in the simulator.
If there's no way to do this, then please can someone tell me how to have the test harness not instantiate the whole app? My app is event driven, and it sends a bunch of events through when it starts up that mess with my tests.
Please can someone tell me how to have the test harness not instantiate the whole app? My app is event driven, and it sends a bunch of events through when it starts up that mess with my tests.
I use Xcode 4's built-in testing. App instantiation may seem like a pain, but as I write on Xcode Unit Testing: The Good, the Bad, the Ugly, it makes it possible to write tests without distinguishing between logic tests and application tests. Specifically, it lets me write unit tests for view controllers.
Here's what I do to avoid my full startup sequence:
Edit the scheme
Select the Test action
In "Test" select the Arguments tab
Disable "Use the Run action's options"
Add an environment variable, setting runningTests to YES
Edit your app delegate
Add the following to -application:didFinishLaunchingWithOptions: as soon as it makes sense to:
#if DEBUG
if (getenv("runningTests"))
return YES;
#endif
Do the same for -applicationDidBecomeActive: but simply return.
Update: I have changed my approach. See How to Easily Switch Your App Delegate for Testing.
In the last xcode version (5.0.2) you can do this in very easy way. Choose your Test target, "General" tab. Set "None" in field "Target". Then tap on "Build phases" tab and remove your Main target from "Target dependencies".
In your situation, I am assuming that you have a separate Logic Tests and Application Tests target (if not - you need to). In your schemes configuration you define which targets are built for the 'Test' scheme. If your application tests are not running, the simulator will not launch.
I suspect that you might be trying to run 'logic tests' in an 'Application tests' target (such as the one created by default by Xcode). See more about this difference here (and how to set ut up).
It was pointed out in an earlier answer that logic tests are the right thing to do for this scenario. I had very tough time in getting the logic tests working with XCode Version 4.3.2 (4E2002). Looking at Apple's sample unit test project helped me to understand how to do this with a clear separation. In that example, logic tests test files from the library target, not the application target. The model was encapsulated into a library which was then linked with the main target and logic tests target. The application target contained only views and controllers.
Based on this model, this is what I did to get my logic tests work correctly. Create a new target (Cocoa Touch Static Library) and move all files to be logic tested (typically all your models) to this new target. Under "Build Phases" settings add this new library in "Link Binary With Libraries" of your application target and logic tests target.
I can imagine that these instructions are little confusing. If you dissect the sample project that is mentioned above you will get a better idea.
Note, untested on Xcode 5.
I used #jon-reid’s answer, but found that Xcode adds environment-variables to the xcuserstated part of XcodeProjects, and these are user specific and not typically committed to the repository. Thus I swizzle my AppDelegate to override its loading:
#implementation MyAppDelegate (Testing)
+ (void)initialize {
SEL new = #selector(application:didFinishLaunchingWithOptions:);
SEL orig = #selector(swizzled_application:didFinishLaunchingWithOptions:);
Class c = [self class];
Method origMethod = class_getInstanceMethod(c, orig);
Method newMethod = class_getInstanceMethod(c, new);
if (class_addMethod(c, orig, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))) {
class_replaceMethod(c, new, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
} else {
method_exchangeImplementations(origMethod, newMethod);
}
}
- (BOOL)swizzled_application:(id)app didFinishLaunchingWithOptions:(id)opts {
return YES;
}
#end
Note, that the following is simpler and still works, though I'm not sure it is reliable:
#implementation MyAppDelegate (Testing)
- (BOOL)application:(id)app didFinishLaunchingWithOptions:(id)opts {
return YES;
}
#end
This works because categories of methods in dynamically loaded components (like the testing bundle) take precedence. Swizzling feels safer though.
Using xCode 7 and xctool
xctool is capable of executing unit tests without the simulator.
To get this working,
1 . Update the target settings run without a host app.
Select your project --> then test target --> Set the host application to none.
2. Install xctool , if you don't have it.
brew install xctool
3. Run the tests using terminal with xctool.
xctool -workspace yourWorkspace.xcworkspace -scheme yourScheme run-tests -sdk iphonesimulator
i've used GHUnit to create osx/ios compatible test suites. there are a few issues, but i found it was more reliable/compatible/straightforward than OCUnit.
GHUnit provides basic template projects for OS X and iOS, which makes initial setup simple.
Note: I generally just use my own kit for most of my testing.