Compare commits

...

10 Commits

  1. 2
      .gitignore
  2. 24
      Makefile
  3. 7
      NEWS.md
  4. 10
      README.md
  5. 227
      archinfo.c

2
.gitignore

@ -1,5 +1,7 @@
archinfo archinfo
.DS_Store
# Xcode # Xcode
# #
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore

24
Makefile

@ -1,11 +1,31 @@
# replace this with yours if you want to codesign your own binary
IDENTITY="Apple Development: Bob Rudis (9V3BZ2VH79)"
archinfo: archinfo:
$(CC) archinfo.c -o x86_app -target x86_64-apple-macos10.12 $(CC) archinfo.c -o x86_app -target x86_64-apple-macos10.12
$(CC) archinfo.c -o arm_app -target arm64-apple-macos11 $(CC) archinfo.c -o arm_app -target arm64-apple-macos11
lipo -create -output archinfo x86_app arm_app && rm x86_app arm_app lipo -create -output archinfo x86_app arm_app && rm x86_app arm_app
leaks: archinfo
leaks --readonlyContent -atExit -- ./archinfo | grep LEAK: || true
leaks --readonlyContent -atExit -- ./archinfo --json | grep LEAK: || true
leaks --readonlyContent -atExit -- ./archinfo --json | grep LEAK: || true
sign: archinfo
codesign --force --verify --verbose --sign ${IDENTITY} archinfo
clean: clean:
rm -f archinfo rm -f archinfo
install: archinfo
codesign --force --verify --verbose --sign ${IDENTITY} archinfo
cp archinfo /usr/local/bin
test: archinfo test: archinfo
@./archinfo | grep -q tccd && echo "Columns: PASSED" || "Columns: FAILED" @./archinfo | grep -q tccd && echo "Columns: PASSED (list)" || echo "Columns: FAILED (list)"
@./archinfo --json | grep -q 'tccd"}' && echo " JSON: PASSED" || " JSON: FAILED" @./archinfo --columns | grep -q tccd && echo "Columns: PASSED (list, explicit)" || echo "Columns: FAILED (list, explicit)"
@./archinfo --json | grep -q 'tccd"}' && echo " JSON: PASSED (list)" || echo " JSON: FAILED (list)"
@(./archinfo --pid `pgrep keyboardservicesd` | grep -q '64') && echo "Columns: PASSED (single)" || echo "Columns: FAILED (single)"
@(./archinfo --columns --pid `pgrep keyboardservicesd` | grep -q '64') && echo "Columns: PASSED (single, explicit)" || echo "Columns: FAILED (single, explicit)"
@(./archinfo --json --pid `pgrep keyboardservicesd` | grep -q '"}') && echo " JSON: PASSED (single)" || echo " JSON: FAILED (single)"

7
NEWS.md

@ -1,5 +1,12 @@
* 0.4.0 • 2021-05-25
- added options for only showing X86_64 or ARM64 processes
# 0.3.0 • 2021-03-14
- added option for retrieving process info for a single pid
- added more tests
- added leak check Makefile option
# 0.2.0 • 2021-03-14 # 0.2.0 • 2021-03-14
- removed Xcode dependency - removed Xcode dependency
- added codesigning option
- added option for either columnar output or ndjson output - added option for either columnar output or ndjson output
# 0.1.0 • 2021-03-13 # 0.1.0 • 2021-03-13

10
README.md

@ -6,7 +6,7 @@ Apple M1/Apple Silicon/arm64 macOS can run x86_64 programs via Rosetta and most
Activity Monitor can show the architecture, but command line tools such as `ps` and `top` do not due to Apple hiding the details of the proper `sysctl()` incantations necessary to get this info. Activity Monitor can show the architecture, but command line tools such as `ps` and `top` do not due to Apple hiding the details of the proper `sysctl()` incantations necessary to get this info.
Patrick Wardle reverse engineered Activity Monitor<https://www.patreon.com/posts/45121749> — and I slapped that hack together with some code from Sydney San Martin — https://gist.github.com/s4y/1173880/9ea0ed9b8a55c23f10ecb67ce288e09f08d9d1e5 — into a nascent, bare-bones command line utility `archinfo`. Patrick Wardle reverse-engineered Activity Monitor <https://www.patreon.com/posts/45121749> — and I slapped that hack into a bare-bones command line utility `archinfo`.
It returns columnar output or JSON (via `--json`) — that will work nicely with `jq` — of running processes and their respective architectures. It returns columnar output or JSON (via `--json`) — that will work nicely with `jq` — of running processes and their respective architectures.
@ -27,6 +27,14 @@ $ archinfo
... ...
``` ```
```bash
$ archinfo --pid $(pgrep keyboardservicesd)
60298 x86_64 /usr/libexec/keyboardservicesd
$ archinfo --json --pid $(pgrep keyboardservicesd)
{"pid":60298,"arch":"x86_64","name":"/usr/libexec/keyboardservicesd"}
```
``` ```
Rscript -e 'table(jsonlite::stream_in(textConnection(system("/usr/local/bin/archinfo --json", intern=TRUE)))$arch)' Rscript -e 'table(jsonlite::stream_in(textConnection(system("/usr/local/bin/archinfo --json", intern=TRUE)))$arch)'
## ##

227
archinfo.c

@ -1,14 +1,32 @@
#include <unistd.h>
#include <getopt.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <stdbool.h>
#include <mach/machine.h> #include <mach/machine.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/sysctl.h> #include <sys/sysctl.h>
#include <libgen.h>
#define VERSION "0.4.0"
#define noErr 0 #define noErr 0
#define VERSION "0.2.0" #define DEFAULT_BUFFER_SIZE 4096
#define SYSCTL_ERROR 1
#define is_match(x) (x == 0)
enum fmt { JSON=0, COLUMNS }; enum fmt { JSON=0, COLUMNS };
enum show { ALL=0, ARM64, X86_64 };
static int max_arg_size = 0;
typedef struct ProcInfo {
bool ok;
char *name;
char arch[10];
} procinfo;
// arch_info() is due to the spelunking work of Patrick Wardle <https://www.patreon.com/posts/45121749> // arch_info() is due to the spelunking work of Patrick Wardle <https://www.patreon.com/posts/45121749>
static char *arch_info(pid_t pid) { static char *arch_info(pid_t pid) {
@ -42,7 +60,7 @@ static char *arch_info(pid_t pid) {
if(noErr != sysctl(mib, (u_int)length, &procInfo, &size, NULL, 0)) return("arm64"); //get proc info if(noErr != sysctl(mib, (u_int)length, &procInfo, &size, NULL, 0)) return("arm64"); //get proc info
//'P_TRANSLATED' set? set architecture to 'Intel' //'P_TRANSLATED' set? set architecture to 'x86_64'
return( (P_TRANSLATED == (P_TRANSLATED & procInfo.kp_proc.p_flag)) ? "x86_64" : "arm64"); return( (P_TRANSLATED == (P_TRANSLATED & procInfo.kp_proc.p_flag)) ? "x86_64" : "arm64");
} }
@ -51,61 +69,104 @@ static char *arch_info(pid_t pid) {
} }
// The below is modified code from: <https://gist.github.com/s4y/1173880/9ea0ed9b8a55c23f10ecb67ce288e09f08d9d1e5> // retrieve process info
// and is Copyright (c) 2020 DeepTech, Inc. (MIT License).
// procinfo proc_info(pid_t pid) {
// The code below the standard way to do this and I originally only copied it due to some Objective-C components.
// While those components are no longer in use, the ACK stays b/c I'm thankful I didn't have to write Objective-C size_t size = max_arg_size;
// for the initial version :-) procinfo p;
int mib[3] = { CTL_KERN, KERN_PROCARGS2, pid };
struct kinfo_proc *info;
size_t length;
int count;
// get size for buffer
(void)sysctl(mib, 3, NULL, &length, NULL, 0);
int enumerate_processes(enum fmt output_type) { char* buffer = (char *)calloc(length+32, sizeof(char)); // need +32 b/c this is busted on Big Sur
static int maxArgumentSize = 0; // get the info
if (sysctl(mib, 3, buffer, &size, NULL, 0) == 0) {
if (maxArgumentSize == 0) {
size_t size = sizeof(maxArgumentSize); // copy the info
if (sysctl((int[]) { CTL_KERN, KERN_ARGMAX }, 2, &maxArgumentSize, &size, NULL, 0) == -1) { p.ok = TRUE;
perror("sysctl argument size"); p.name = buffer;
maxArgumentSize = 4096; // Default strncpy(p.arch, arch_info(pid), 10);
}
} else {
free(buffer);
p.ok = FALSE;
}
return(p);
}
// output one line of process info with architecture info
void output_one(enum fmt output_type, pid_t pid, procinfo p, bool only_basename) {
if (output_type == COLUMNS) {
printf(
"%7d %6s %s\n",
pid,
p.arch,
(only_basename ? basename((char *)(p.name+sizeof(int))) : p.name+sizeof(int))
);
} else if (output_type == JSON) {
printf(
"{\"pid\":%d,\"arch\":\"%s\",\"name\":\"%s\"}\n",
pid,
p.arch,
(only_basename ? basename((char *)(p.name+sizeof(int))) : p.name+sizeof(int))
);
} }
}
// walk through process list, get PID and name, then pass that on to output_one() to
// grab the architecture and do the actual output
int enumerate_processes(enum fmt output_type, enum show to_show, bool only_basename) {
int mib[3] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL }; int mib[3] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL };
struct kinfo_proc *info; struct kinfo_proc *info;
size_t length; size_t length;
int count; int count;
if (sysctl(mib, 3, NULL, &length, NULL, 0) < 0) return(1); // get the running process list
if (sysctl(mib, 3, NULL, &length, NULL, 0) < 0) return(SYSCTL_ERROR);
if (!(info = malloc(length))) return(1); // make some room for results
if (!(info = calloc(length, sizeof(char)))) return(SYSCTL_ERROR);
// get the results
if (sysctl(mib, 3, info, &length, NULL, 0) < 0) { if (sysctl(mib, 3, info, &length, NULL, 0) < 0) {
free(info); free(info);
return(1); return(SYSCTL_ERROR);
} }
// how many results?
count = (int)(length / sizeof(struct kinfo_proc)); count = (int)(length / sizeof(struct kinfo_proc));
// for each result
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
pid_t pid = info[i].kp_proc.p_pid; pid_t pid = info[i].kp_proc.p_pid; // get PID
if (pid == 0) continue; if (pid == 0) continue;
size_t size = maxArgumentSize; procinfo p = proc_info(pid); // get process info
char* buffer = (char *)malloc(length);
if (sysctl((int[]){ CTL_KERN, KERN_PROCARGS2, pid }, 3, buffer, &size, NULL, 0) == 0) { if (p.ok) {
if (output_type == COLUMNS) { if (
printf("%7d %6s %s\n", pid, arch_info(pid), buffer+sizeof(int)); (to_show == ALL) ||
} else if (output_type == JSON) { ((to_show == ARM64) && is_match(strncmp("arm", p.arch, 3))) ||
printf("{\"pid\":%d,\"arch\":\"%s\",\"name\":\"%s\"}\n", pid, arch_info(pid), buffer+sizeof(int)); ((to_show == X86_64) && is_match(strncmp("x86", p.arch, 3)))
) {
output_one(output_type, pid, p, only_basename);
} }
free(p.name);
} }
free(buffer);
} }
free(info); free(info);
@ -114,36 +175,110 @@ int enumerate_processes(enum fmt output_type) {
} }
// call to display cmdline tool help
void help() { void help() {
printf("archinfo %s\n", VERSION); printf("archinfo %s\n", VERSION);
printf("boB Rudis <bob@rud.is>\n"); printf("boB Rudis <bob@rud.is>\n");
printf("\n"); printf("\n");
printf("archinfo outputs a list of running processes with the architecture (x86_64/amd64)\n"); printf("archinfo outputs a list of running processes with architecture (x86_64/amd64) information\n");
printf("\n"); printf("\n");
printf("USAGE:\n"); printf("USAGE:\n");
printf(" archinfo [FLAG]\n"); printf(" archinfo [--arm|--x86] [--basename] [--columns|--json] [--pid #]\n");
printf("\n"); printf("\n");
printf("FLAG:\n"); printf("FLAGS/OPTIONS:\n");
printf(" --arm Only show arm64 processes (default is to show both)\n");
printf(" --x86 Only show x86_64 processes (default is to show both)\n");
printf(" --basename Only show basename of process executable\n");
printf(" --columns Output process list in columns (default)\n");
printf(" --json Output process list in ndjson\n"); printf(" --json Output process list in ndjson\n");
printf(" --columns Output process list in columns\n"); printf(" --pid # Output process architecture info for the specified process\n");
printf(" --help Display this help text\n"); printf(" --help Display this help text\n");
} }
void init() {
if (max_arg_size == 0) {
size_t size = sizeof(max_arg_size);
if (sysctl((int[]) { CTL_KERN, KERN_ARGMAX }, 2, &max_arg_size, &size, NULL, 0) == -1) {
perror("sysctl argument size");
max_arg_size = DEFAULT_BUFFER_SIZE;
}
}
}
int main(int argc, char** argv) { int main(int argc, char** argv) {
if (argc >= 2) { int c;
if (strcmp("--json", argv[1]) == 0) { enum show to_show = ALL;
return(enumerate_processes(JSON)); bool show_help = FALSE;
} else if (strcmp("--columns", argv[1]) == 0) { bool do_one = FALSE;
return(enumerate_processes(COLUMNS)); bool only_basename = FALSE;
} else if (strcmp("--help", argv[1]) == 0) { pid_t pid = -1;
help(); enum fmt output_type = COLUMNS;
return(0);
while(true) {
int this_option_optind = optind ? optind : 1;
int option_index = 0;
static struct option long_options[] = {
{ "arm", no_argument, 0, 'a' },
{ "x86", no_argument, 0, 'x' },
{ "basename", no_argument, 0, 'b' },
{ "json", no_argument, 0, 'j' },
{ "columns", no_argument, 0, 'c' },
{ "help", no_argument, 0, 'h' },
{ "pid", required_argument, 0, 'p' },
{ 0, 0, 0, 0 }
};
c = getopt_long(argc, argv, "axbjchp:", long_options, &option_index);
if (c == -1) break;
switch(c) {
case 'a': to_show = ARM64; break;
case 'x': to_show = X86_64; break;
case 'b': only_basename = TRUE; break;
case 'p': do_one = TRUE; pid = atoi(optarg); break;
case 'h': show_help = TRUE; break;
case 'j': output_type = JSON; break;
case 'c': output_type = COLUMNS; break;
} }
} else {
return(enumerate_processes(COLUMNS)); }
init();
// only show help if --help is in the arg list
if (show_help) {
help();
return(0);
} }
// only do one process if --pid is in the arg list
if (do_one) {
if (pid > 0) {
procinfo p = proc_info(pid);
if (p.ok) {
output_one(output_type, pid, p, only_basename);
free(p.name);
return(0);
}
}
return(SYSCTL_ERROR);
}
// otherwise do them all
return(enumerate_processes(output_type, to_show, only_basename));
} }

Loading…
Cancel
Save