main.mm   [plain text]

// BUILD(macos): $CC foo.c -o $BUILD_DIR/libfoo.dylib -dynamiclib -install_name $RUN_DIR/lifoo.dylib
// BUILD(macos): $CC target.c -o $BUILD_DIR/dyld_usage_target.exe -DRUN_DIR="$RUN_DIR"
// BUILD(macos): $CXX main.mm -o $BUILD_DIR/dyld_usage_json.exe -DRUN_DIR="$RUN_DIR" -std=c++14 -framework Foundation

// BUILD(ios,tvos,watchos,bridgeos):

// RUN: $SUDO ./dyld_usage_json.exe

#import <Foundation/Foundation.h>

#include <errno.h>
#include <signal.h>
#include <spawn.h>
#include <sys/wait.h>
#include <unistd.h>

#include <map>
#include <sstream>
#include <string>
#include <vector>

#include "test_support.h"

enum class NodeValueType {
Default,
String,
RawValue,
};

struct Node
{
NodeValueType type = NodeValueType::Default;
std::string value;
std::map<std::string, Node> map;
std::vector<Node> array;

inline Node()
: type(NodeValueType::Default), value(), map(), array() { }

inline Node(std::string string)
: type(NodeValueType::String), value(string), map(), array() { }

inline Node(const char *string) : Node(std::string(string)) { }

inline Node(bool b)
: type(NodeValueType::RawValue), value(b ? "true" : "false")
, map(), array() { }

inline Node(int64_t i64)
: type(NodeValueType::RawValue), value(), map(), array()
{
std::ostringstream os;
os << i64;
value = os.str();
}

inline Node(uint64_t u64)
: type(NodeValueType::RawValue), value(), map(), array()
{
std::ostringstream os;
os << u64;
value = os.str();
}
};

static Node parseNode(id jsonObject) {
__block Node node;

// NSDictionary -> map
if ([jsonObject isKindOfClass:[NSDictionary class]]) {
NSDictionary* dict = (NSDictionary*)jsonObject;

[dict enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL* stop) {
if (![key isKindOfClass:[NSString class]]) {
fprintf(stderr, "JSON map key is not of string type\n");
*stop = true;
return;
}
Node childNode = parseNode(value);

node.map[[key UTF8String]] = childNode;
}];

return node;
}

// NSArray -> array
if ([jsonObject isKindOfClass:[NSArray class]]) {
NSArray* array = (NSArray*)jsonObject;

[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL * stop) {
Node childNode = parseNode(obj);
node.array.push_back(childNode);
}];

return node;
}

// NSString -> value
if ([jsonObject isKindOfClass:[NSString class]]) {
node.value = [(NSString*)jsonObject UTF8String];
return node;
}

fprintf(stderr, "Unknown json deserialized type\n");
return Node();
}

Node readJSON(const void * contents, size_t length) {
NSData* data = [NSData dataWithBytes:contents length:length];
NSError* error = nil;
id jsonObject = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error];
if (!jsonObject) {
fprintf(stderr, "Could not deserialize json because '%s'",[[error localizedFailureReason] UTF8String]);
return Node();
}

return parseNode(jsonObject);
}

char* mergeJsonRoots(char* jsonBuffer, size_t size)
{
char *mergedJson = (char*)malloc((size + 2) * sizeof(char));
mergedJson[0] = '[';
mergedJson[size+1] = '\0';
mergedJson[size+1] = ']';
for (size_t i = 0; i < size; i++) {
mergedJson[i+1] = jsonBuffer[i];
if (jsonBuffer[i] == '\n') {
if ( i > 0 && i < size - 1 ) {
if (jsonBuffer[i-1] == '}' && jsonBuffer[i+1] == '{')
mergedJson[i+1] = ',';
}
}
}
return mergedJson;
}

void validateJson(Node json, pid_t pid)
{
size_t expectedSize = 4;
if (json.array.size() != expectedSize)
FAIL("dyld_usage reported number of events is incorrect. Reported %lu instead of %lu", json.array.size(), expectedSize);

std::string handle = json.array[1].map["event"].map["result"].value;

for (size_t i = 0; i < json.array.size(); i++) {

if ( json.array[i].map["command"].value.compare("dyld_usage_target.exe") != 0 )
FAIL("Incorrect command name for event at index %lu", i);

int jpid = std::stoi(json.array[i].map["pid"].value);
if ( jpid != pid)
FAIL("Incorrect pid for event at index %lu. Reported %d intead of %d (%s)", i, jpid, pid, json.array[i].map["pid"].value.c_str());

if (i == 0) {
if ( json.array[i].map["event"].map["type"].value.compare("app_launch") != 0 )
FAIL("dyld_usage did not report app launch event");
}

if (i == 1) {
if ( json.array[1].map["event"].map["type"].value.compare("dlopen") != 0 )
FAIL("dyld_usage did not report dlopen event");
if ( json.array[1].map["event"].map["path"].value.compare(RUN_DIR "/libfoo.dylib") != 0 )
FAIL("Incorrect dlopen library path");
}

if (i == 2) {
if ( json.array[i].map["event"].map["type"].value.compare("dlsym") != 0 )
FAIL("dyld_usage did not report dlsym event");
if ( json.array[i].map["event"].map["symbol"].value.compare("foo") != 0 )
FAIL("incorrect dlsym symbol reported");
if ( json.array[i].map["event"].map["handle"].value.compare(handle) != 0 )
FAIL("dlsym handle does not match dlopen result");
}

if (i == 3) {
if ( json.array[i].map["event"].map["type"].value.compare("dlclose") != 0 )
FAIL("dyld_usage did not report dlclose event");
if ( json.array[i].map["event"].map["handle"].value.compare(handle) != 0 )
FAIL("dlclose handle does not match dlopen result");
}
}
}

int main(int argc, const char* argv[], char *env[])
{
_process dyldUsage;
dyldUsage.set_executable_path("/usr/local/bin/dyld_usage");
dyldUsage.set_launch_async(true);
const char* args[] = { "-j", "dyld_usage_target.exe", NULL };
dyldUsage.set_args(args);
__block dispatch_data_t output = NULL;
dyldUsage.set_stdout_handler(^(int fd) {
ssize_t size = 0;
do {
char buffer[16384] = {0};
size = read(fd, buffer, 16384);
if ( size == -1 )
break;
dispatch_data_t data = dispatch_data_create(buffer, size, NULL, DISPATCH_DATA_DESTRUCTOR_DEFAULT);
output = output ? dispatch_data_create_concat(output, data) : data;
} while ( size > 0 );
});

pid_t pid = dyldUsage.launch();
usleep(2000000);

// Launch target
_process target;
target.set_executable_path(RUN_DIR "/dyld_usage_target.exe");
pid_t tpid = target.launch();

usleep(2000000);
// Kill dyld_usage
kill(pid, SIGTERM);

int status;
if (waitpid(pid, &status, 0) == -1)
FAIL("waitpid failed");
if ( !output )
FAIL("No dyld_usage output");

const void* buffer;
size_t size;
(void)dispatch_data_create_map(output, &buffer, &size);
char* jsonBuffer = mergeJsonRoots((char*)buffer, size);
size += 2;
Node node = readJSON(jsonBuffer, size);
free(jsonBuffer);
validateJson(node, tpid);

PASS("Success");
return 0;
}