Irritating segfault.

Prepare yourself for the most retarded segfault ever:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
char* get_manufacturer(const char* vstring) {
    if (strcmp(AMD_OLD, vstring) == 0 || strcmp(AMD_CURRENT, vstring) == 0)
        return "AMD";
    if (strcmp(CENTAUR, vstring) == 0)
        return "Centaur";
    if (strcmp(CYRIX, vstring) == 0)
        return "Cyrix";
    if (strcmp(INTEL, vstring) == 0)
        return "Intel";
    if (strcmp(TRANSMETA_1, vstring) == 0 || strcmp(TRANSMETA_2, vstring) == 0)
        return "Transmeta";
    if (strcmp(NAT_SEMICON, vstring) == 0)
        return "National Semiconductor";
    if (strcmp(NEXGEN, vstring) == 0)
        return "NexGen";
        
    return "Unknown";
}

cpuid_t do_cpuid(void) {
    cpuid_t stcpuid;
    void* pvstring = (void*)stcpuid.vendor_string;

    asm volatile(
        "mov    $0,    %%eax\n\t"
        "cpuid              \n\t"
        "mov    %0,    %%rsi\n\t"
        "mov    %%ebx,    0(%%rsi)\n\t"
        "mov    %%edx,    4(%%rsi)\n\t"
        "mov    %%ecx,    8(%%rsi)\n\t"
        :"=m"(pvstring)
    );

    printf("%s", get_manufacturer(stcpuid.vendor_string));
    
    return stcpuid;
}

If I comment out either the asm code or the printf() or both; the segfault goes away (but obviously I don't get the benefits of the function having done what it's meant to be). But if I leave the code as it is now, it segfaults.

Now; the function get_manufacturer can't possibly cause a segfault. The string is a constant and at no point do I attempt to modify it. It consists entirely of a couple of strcmp()s and return statements.
However if I comment out the call to it, the segfault goes away. If I have it just as a call and not as a parameter to printf() I still get a segfault. If I call it with some random text I made up, it still segfaults. Whatever happens, if I have a call to that function AND the ASM, I will get a segfault. But if I remove either or both of them, I don't.

It's like one piece of code has a weird affect on the other! But that doesn't make sense! How can that ASM cause the C to segfault and vice versa? Urgh!


Edit: If I take out the return statement it doesn't segfault either. So now I have three peices of code that are incompatible with each other, but work individually. Note: if you want to try the code out yourself; you'll have to change rsi to esi. You won't have rsi if you have an x86; it's a 64-bit register. Full source below.

Oh, by the way, the segfault occurs after the printf()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>

/* List of known Vendor Strings */
#define AMD_OLD     "AMDisbetter!" /* Truer words were never stuck inside a piece of silicon... */
#define AMD_CURRENT "AuthenticAMD" /* I prefer the other one tbh */
#define CENTAUR     "CentaurHauls"
#define CYRIX       "CyrixInstead"
#define INTEL       "GenuineIntel" /* How boring. Just what happened to
                                    * "HOLY SHIT INTEL CORE 2 EXTREEEEEEEEEEEEME"?
                                    * Oh wait, that's a marketing technique. I get it.
                                    */
#define TRANSMETA_1 "TransmetaCPU"
#define TRANSMETA_2 "GenuineTMx86" /* Huh. They spelt "GenuineIntel" wrong... */
#define NAT_SEMICON "Geode by NSC"
#define NEXGEN      "NexGenDriven"
/* Now we're done with those CPUs made by imaginative people, here come the
 * vendor strings which, frankly, are boring as hell:
 */
#define RISE        "RiseRiseRise" /* OOH IMAGINATIVE */
#define SIS         "SiS SiS SiS " /* How *did* you come up with that? */
#define UMC         "UMC UMC UMC " /* Anyone else noticing a pattern? */
#define VIA         "VIA VIA VIA " /* Blatant plagiarism of awesome vendor string idea is blatant. */

void sighandle(int sig) {
    printf("Your segments;\n\n\nThey have faulted.\n");
    exit(1);
}

typedef struct cpuid {
    char vendor_string[12];
    char manufacturer[24];
}   cpuid_t;

char* get_manufacturer(const char*);
cpuid_t do_cpuid(void);

int main() {
    signal(SIGSEGV, sighandle);
    cpuid_t stcpuid;
    stcpuid = do_cpuid();
    
    printf("Dumping processor information...\n\n");
    printf("Manufacturer:\t%s\tVendor string:\t%s\n",
          stcpuid.manufacturer, stcpuid.vendor_string
          );

    return 0;
}

char* get_manufacturer(const char* vstring) {
    if (strcmp(AMD_OLD, vstring) == 0 || strcmp(AMD_CURRENT, vstring) == 0)
        return "AMD";
    if (strcmp(CENTAUR, vstring) == 0)
        return "Centaur";
    if (strcmp(CYRIX, vstring) == 0)
        return "Cyrix";
    if (strcmp(INTEL, vstring) == 0)
        return "Intel";
    if (strcmp(TRANSMETA_1, vstring) == 0 || strcmp(TRANSMETA_2, vstring) == 0)
        return "Transmeta";
    if (strcmp(NAT_SEMICON, vstring) == 0)
        return "National Semiconductor";
    if (strcmp(NEXGEN, vstring) == 0)
        return "NexGen";
        
    return "Unknown";
}

cpuid_t do_cpuid(void) {
    cpuid_t stcpuid;
    void* pvstring = (void*)stcpuid.vendor_string;

    asm volatile(
        "mov    $0,    %%eax\n\t"
        "cpuid              \n\t"
        "mov    %0,    %%rsi\n\t"
        "mov    %%ebx,    0(%%rsi)\n\t"
        "mov    %%edx,    4(%%rsi)\n\t"
        "mov    %%ecx,    8(%%rsi)\n\t"
        :"=m"(pvstring)
    );

    printf("%s\n", get_manufacturer(stcpuid.vendor_string));
    
    return stcpuid;
}
output:
$ ./cpuid
Intel
Your segments;


They have faulted.

Ignore the unfunny comments...
Last edited on
My fists hurt from beating this thing into working. Not knowing how to use inline Assembly didn't help, either.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <iostream>

#define AMD_OLD "AMDisbetter!"
#define AMD_CURRENT "AuthenticAMD"
#define CENTAUR "CentaurHauls"
#define CYRIX "CyrixInstead"
#define INTEL "GenuineIntel"
#define TRANSMETA_1 "GenuineTMx86"
#define NAT_SEMICON "Geode by NSC"
#define NEXGEN "NexGenDriven"

const char *get_manufacturer(const char *vstring) {
	if (strcmp(AMD_OLD, vstring) == 0 || strcmp(AMD_CURRENT, vstring) == 0)
		return "AMD";
	if (strcmp(CENTAUR, vstring) == 0)
		return "Centaur";
	if (strcmp(CYRIX, vstring) == 0)
		return "Cyrix";
	if (strcmp(INTEL, vstring) == 0)
		return "Intel";
	if (strcmp(TRANSMETA_1, vstring) == 0)
		return "Transmeta";
	if (strcmp(NAT_SEMICON, vstring) == 0)
		return "National Semiconductor";
	if (strcmp(NEXGEN, vstring) == 0)
		return "NexGen";
		
	return "Unknown";
}

int main(){
	unsigned a[3];
	asm volatile(
		"cpuid\n\t"
		:"=b"(a[0]),
		"=d"(a[1]),
		"=c"(a[2])
		:"a"(0)
	);
	char s[13]={0};
	memcpy(s,a,12);
	std::cout <<s<<std::endl;
	std::cout <<get_manufacturer(s)<<std::endl;
	return 0;
}


EDIT: http://www.etallen.com/cpuid.html
Last edited on
Thanks :)
I didn't know you could do that with inline asm without using a bunch of movs.

Anyway now I need to figure out the rest of the stuff you can get with CPUID. I successfully the vendor string and manufacturer into the struct; so I can do the rest myself.
Thanks again.
Last edited on
Just wanted to mention something very weird that happened.

In
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cpuid_t do_cpuid(void) {
    cpuid_t stcpuid;

    unsigned a[3] = {0};

    asm volatile(
        "cpuid\n\t"
        :"=b"(a[0]), "=d"(a[1]), "=c"(a[2])
        :"a"(0)
    );
    
    memmove(stcpuid.vstring, a, 12);
    strncpy(stcpuid.manufacturer, get_manufacturer(stcpuid.vstring), 24);    
    
    return stcpuid;
}

manufacturer is getting appended to vstring (without segfaulting) even though vstring is only 12 bytes!
I fixed it, like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
typedef struct cpuid {
    unsigned cores;
    unsigned hthreads;
    /* eax = 0 */
    char     vstring[12];
    char     padding[24]; /* To avoid a really weird bug where manufacturer was
                           * appended to vstring (without segfaulting :l )
                           */
    char     manufacturer[24];
    /* eax = 1 */
    unsigned stepping;
    unsigned model;
    unsigned family;
    /* eax = 0x80000002, 0x80000003, 0x80000004 */
    char     brand[48];
    /* eax = 0x80000005 */
    size_t   l1_cache;
    /* eax = 0x80000006 */
    size_t   l2_cache;
}   cpuid_t;


But isn't that a weird bug? The fix works, but still. That is just strange.
It's not getting "appended". vstring is exactly 12 characters long, which is also the length of the manufacturer string. Now, tell me: what size did I give s on line 40 and why?

Also, buffer overflows don't always crash a program. This is particularly true for stack buffers. If this wasn't the case, memory bugs wouldn't be that much of a pain to find and debug and they couldn't stay hidden for years as they often do.
helios wrote:
Now, tell me: what size did I give s on line 40 and why?

Because... you're awesome? 13 (12 + 1 for the null) bytes.

I forgot -- the vendor string isn't null-terminated. So then, because there was no null printf() (which prints until a null is found) didn't stop printing and went on into the next memory location? Is it something like that? In which case my "fix" only worked because it was a null-terminated array?
you're awesome
And don't you forget it.

Yes. padding could have been declared as char padding; and initialized to zero and it would have also worked.
Ahhh... Cool. Working things out is fun. :)

And don't you forget it.

I won't :( -feels intimidated-
Just one last thing I can't resist mentioning - and this isn't a rant at you, so don't misinterpret it - Firstly, I may have misread the code you linked me too, and I may be exaggerating here or just out-and-out wrong, but what the hell has that guy actually done to get the cpuid? As far as I can tell, excuse the language: fuck all. He opened a bunch of files that Linux stores about the CPU (meaning his program is confined to UNIXes, or maybe Linux and Minix, or maybe even just Linux -- whereas mine will work on any x64 or x86 machine -- I'm not even bragging - what is there to brag about typing cpuid and moving stuff, around (and getting help with it), but really, even I'm doing more work than him!
Again, I stress that really it isn't difficult; but at least mine deserves to be called a cpuid program! Or, will anyway. And his is all in one source file. What is that meant to make it look more complicated? Or can't he use header files? Again maybe I'm just dumb or didn't read the source properly...
*Shrug*
I only linked it because it was an example of a complete CPUID call. It succeeds at that, if nothing else.

If you'll excuse my tangent, allow me to describe the worst program I've ever seen. There's two programs floating around out there. One is the reverse operation of the other. These programs actually manage to apply an XOR 0x84 (as a sort of keyless encryption) slowly, by doing it like this:
Program A (encryption):
1. Read one byte from input.
2. If byte is 13, read another byte.
3. If sequence is CRLF, write 10^0x84 to output
4. Otherwise write byte[0]^0x84 and byte[1]^0x84 to output
Program B (decryption):
1. Read one byte from input.
2. If byte^0x84 is 10, Write 13 and 10 to the output.
3. Otherwise write byte^0x84 to the output.

The first time I used it, it took 20 minutes to process 4 MiB on my old K6-2, so I naturally assumed it was doing something useful with all that time. When I read the source of a different, but related program, I was pissed. Needless to say, my own implementation took one and a half seconds to finish on the same computer. Around a thousand times faster.
I'm not asking for hand-optimized Assembly, but when your program XORs at .2 MiB/minute, there's something seriously wrong with your code.
As if the programmer was personally trying to piss me off, it generates console output in Shift JIS. I will eat my own heart and come back as a demon of vengeance to collect and devour the souls of every programmer who has ever used Shift JIS as the main encoding in their program.
helios wrote:
*Shrug*
I only linked it because it was an example of a complete CPUID call. It succeeds at that, if nothing else.

It was a valid program; it just irritated me (massively) because it didn't even perform cpuid once, and yet was still called "cpuid". It isn't cpuid, I think "open a bunch of files and play around with the information inside them and make it look like we're doing cpuid" would be a better name.

If you'll excuse my tangent, allow me to describe the worst program I've ever seen.

Ok!

The first time I used it, it took 20 minutes to process 4 MiB

Now that's efficient! I wouldn't suffer a program to run for more than two minutes without any output. (Edit: unless I was told it would take a long time, it made sense that it would take a long time (e.g. it was doing a MIPS or FLOPS calculation) or it was waiting for input or something, or it was a compiler and I was compiling >500 lines of code with >2 different libraries).

Hence why I would write the output to a file (place in "/tmp" or "C:/Windows/temp" or whatever) and every now again print something like this:
Read file...
Parse contents of file...
Processing...
========================> done.
Written temporary file...
Dump contents to standard output:
<output>
Delete temporary file...
Exit...

Or whatever. How could you rely on the user, a human being, and I stress the "human being" part because humans are notoriously impatient, to wait for twenty minutes with no output?

Shift JIS

Is that slower? Or is it just because they unnecessarily used Japanese encoding?
Last edited on
Now that's efficient! I wouldn't suffer a program to run for more than two minutes without any output.
Oh, right! Of course. I completely forgot that little detail. The programmer felt it necessary to print the current offset every fucking time through the loop.

http://en.wikipedia.org/wiki/Shift_JIS
SJIS's been unnecessary since the advent of UCS, UTF-8, and -16. And yet there's still software being written that uses it. And get this: I've heard that the Japanese complain because the CKJ ideographs where unified into a single block. Apparently, they wanted their codepoints separate from the Chinese's. Is that being an asshole or what?
No one would have ever done it like that, anyway. The CKJ block is most of the 0000-FFFF range ([4E00;FA2D]). No one is going to duplicate that thing for no adequate reason.
helios wrote:
Oh, right! Of course. I completely forgot that little detail. The programmer felt it necessary to print the current offset every fucking time through the loop.

What the hell??? So that's like me going
1
2
3
4
for (int i = 0; i < SOME_NUMBER; i++) {
    /* Do some stuff */
    std::cout << i << "\n";
}

Wow. Someone is good at what they do!
Admittedly that could have been for debugging purposes that he forgot to take out -- I've done that before. Then again; I'm not meant to be making production-quality programs.
Debugging? No, that excuse doesn't work, here. There are several '\b' just before writing the number so that it stays on the same line. Someone's been watching too many movie/TV computer interfaces. "This is UNIX. I know this!"
Lol, really? It just prints on the same line? I'm guilty of having done that; but only because I had output that wouldn't have made sense on separate lines; it was a counter: "Exiting in %d", that kinda thing.

Edit: I bet that's the kind of person who spends half an hour getting colours to work (on xterm and on windows, probably with #ifdefs) so that they can have the "classic" green text? Without realising that MS-DOS used 0x0F ("white" (grey) on black)?
Last edited on
Topic archived. No new replies allowed.