Querying for electronics relevant information of my MacBook via native code

Howdy!

I have been experimenting with my system's electronics (MacBook Pro 13-inch, M1, 2020) by using software probe written in C/C++ type program. The idea is to extract the relevant information just like it is presented in

Apple > About This Mac > System Report,

for my cross-platform Engine.

I have been tinkering with a tool dmidecode. It works just fine on Linux and Windows. For MacOS, I need to find a graceful way of obtaining the AppleSMBIOS service, in this context.

To my best of knowledge, the service is nonexistent as I have checked by C code and some application named IORegistryExplorer (courtesy Google).

Just wondering if someone has been on such endurance and how much satisfaction has been achieved upon the topic.

Thanks a lot!

Answered by DTS Engineer in 732366022

WARNING The I/O Registry presents significant binary compatibility challenges. Rummaging around the registry and pulling out undocumented properties is not something that we support. It might work today on your specific Mac, but it might not work on other machines or in previous or future OS releases.

With that out of the way, let’s see what we can do to help. You wrote:

void* someValuePointer = malloc(CFNumberGetByteSize(someStuff));
if(CFNumberGetValue(someStuff, CFNumberGetType(someStuff), someValuePointer))

That code is kinda weird. A CFNumber holds an abstract numeric value. While you can look at the specific type of number it holds, in most case you don’t need to. Rather, you ask for the type you want and CFNumber does the conversion. For example:

CFNumberRef n1234 = … a CFNumber with the value 1234 …
int64_t i64 = 0;
Boolean success = CFNumberGetValue(n1234, kCFNumberSInt64Type, &i64);
if ( ! success ) {
    // … handle the error …
}
printf("%lld\n", (long long) i64);

Also, when dealing with Core Foundation types the best you can do is leave ‘CF Land’ as quickly as possible and do all the heavy lifting in Objective-C. That yields a much nicer API:

CFNumberRef cf = … a CFNumber with a +0 reference count …
NSNumber * ns =  (__bridge NSNumber *) cf;
NSUInteger u = ns.unsignedIntegerValue;

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Howdy!

I also have a follow up question. I am retrieving a property value of type Number (as indicated in the registry) like so

Property Type Value IOCPUID Number 0x100000256

Now I am trying to cast the value to C style char * type like so

// Retrieve the value from property
CFDictionaryGetValueIfPresent(propertiesDict, fetchingKey, &valuePointer)

CFNumberRef someStuff = (CFNumberRef) valuePointer; // <- contains the retrieved value
void* someValuePointer = malloc(CFNumberGetByteSize(someStuff));
if(CFNumberGetValue(someStuff, CFNumberGetType(someStuff), someValuePointer))
{
   printf("The Number value is %s.\n", ((char *)someValuePointer));
}
else
{
	printf("No suitable conversion found.");
}
free(someValuePointer);

The debugger reports the rightly.

dataType	MacPropertyDataTypes	NumberType
someStuff	CFNumberRef	429496****	0xaf1484d3b2c09d6d
valuePointer	__NSCFNumber *	429496**** 0xf1484d3b2c096d
someValuePointer void* 0x2f6e8001845*****

Things are quite clear till valuePointer. I can see the true value. But then everything is downhill after that. It seems "CFNumberGetValue" is not doing the right conversion? Because logs are like so

The service AppleARMCPU, with field CFBundleIdentifier has the value com.apple.driver.AppleARMPlatform. 
The service AppleARMCPU, with field IOMatchCategory has the value IODefaultMatchCategory. 
CFNumber value 429496****, type = kCFNumberSInt64Type
The Number value is 598.

The number 598, whatever that is, makes no sense to me. A little clue as to what is happening should help a ton!

Thanks Apple Fan

Accepted Answer

WARNING The I/O Registry presents significant binary compatibility challenges. Rummaging around the registry and pulling out undocumented properties is not something that we support. It might work today on your specific Mac, but it might not work on other machines or in previous or future OS releases.

With that out of the way, let’s see what we can do to help. You wrote:

void* someValuePointer = malloc(CFNumberGetByteSize(someStuff));
if(CFNumberGetValue(someStuff, CFNumberGetType(someStuff), someValuePointer))

That code is kinda weird. A CFNumber holds an abstract numeric value. While you can look at the specific type of number it holds, in most case you don’t need to. Rather, you ask for the type you want and CFNumber does the conversion. For example:

CFNumberRef n1234 = … a CFNumber with the value 1234 …
int64_t i64 = 0;
Boolean success = CFNumberGetValue(n1234, kCFNumberSInt64Type, &i64);
if ( ! success ) {
    // … handle the error …
}
printf("%lld\n", (long long) i64);

Also, when dealing with Core Foundation types the best you can do is leave ‘CF Land’ as quickly as possible and do all the heavy lifting in Objective-C. That yields a much nicer API:

CFNumberRef cf = … a CFNumber with a +0 reference count …
NSNumber * ns =  (__bridge NSNumber *) cf;
NSUInteger u = ns.unsignedIntegerValue;

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Yet another followup question.

What is the right way to read off the data from the entries presented like so

I am hoping for some kind of reference table from which information (like numerical or string form of the property clock-frequency) can be extracted word-by-word. Couldn't find much on official Apple's docs.

Some of the values you posted have obvious interpretations. For example, l2-cache-size is clearly a data value carrying a little endian [1] UInt32. You can access such values like so:

NSData * data = … something …
uint32_t value = * (const uint32_t *) data.bytes;

Having said that, there are much nicer ways to get the cache size, names sysctl. You can do this from the command line:

% sysctl -a | grep "hw.*cache"
hw.perflevel0.l1icachesize: 32768
hw.perflevel0.l1dcachesize: 32768
hw.perflevel0.l2cachesize: 262144
hw.perflevel0.l3cachesize: 16777216
hw.cacheconfig: 16 2 2 16 0 0 0 0 0 0
hw.cachesize: 34359738368 32768 262144 16777216 0 0 0 0 0 0
hw.cachelinesize: 64
hw.l1icachesize: 32768
hw.l1dcachesize: 32768
hw.l2cachesize: 262144
hw.l3cachesize: 16777216

but there’s also an API, namely sysctlbyname (see its man page).

The other properties don’t contain any easily recognisable structure. I don’t have any good suggestions on that front.

I also want to reiterate that properties like this are not considered API. Feel free to rummage around and explore but, if you build a product based on this info, you are very likely to run into binary compatibility problems down the line.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] Well, probably native endian, which just happens to be little endian at the moment.

Aha! So I thought. Reverse engineering is not new to me personally. Thanks for the confirmation though!!

About the concern you raise, I do pay attention. I shall use my educational freedom. If you have some clever way to perform same job in alternate way, I am all ears. One way I can think of is dumping the output of command like ioreg -b -w 0 -f -r -c AppleSmartBattery and doing relevant parsing. Let me know your opinions for that.

Thanks again for speedy reply Quinn, much appreciated.

Cheers Apple Fan

Just some information, many sysctlbyname params are spitting error number 2 or 12.

I wonder how System Report gathers the relevant data and if there is some API associated with that.

sysctlbyname returns Posix-style error codes. These are defined in <sys/errno.h>. So, error 2 is ENOENT and error 12 is ENOMEM. See the sysctlbyname man page for info on what those mean.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

So ENOENT is "no such file or directory" and ENOMEM is "Cannot allocate memory.".

For the context, this is how I am approaching

char* queryParam = "hw.cpufrequency_max"

int64_t fetchedValue = 0;
size_t size = sizeof(fetchedValue);

if(sysctlbyname(queryParam, &fetchedValue, &size, NULL, 0) == -1)
{
    printf("BR_ERROR: %s not found with error %d \n", queryParam, errno);
}
else
{
   printf("Value gauged is %lld", fetchedValue);
}

the output is

BR_ERROR: hw.cpufrequency_max not found with error 2

BR stands for BiosReader.

Few remarks

  1. for queryParam = kern.clockrate however, the error is 12 (maybe because of KERN)
  2. for queryParam = hw.cachelinesize I get successful result

The code you posted works on my machine. I put it into a small test command-line tool project and it prints this:

Value gauged is 2400000000

Built with Xcode 14.1 and run on macOS 13.0.1 on Intel.

The kern.clockrate failure is likely because you’re passing in an int64_t where, according to the sysctlbyname man page, it’s expecting a struct clockinfo.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Querying for electronics relevant information of my MacBook via native code
 
 
Q