C
Diesen Code habe ich mal vor einiger Zeit im Netz gefunden. Man kann den CPU-Speed und CPU-Type ermitteln. Der Code ist für Visual C++ 6, sollte sich aber auch an andere Compiler anpassen lassen. Soweit vorhanden ist die Quelle im Header, dieser Code stammt nicht aus meiner Feder. Da das hier grossenteils Assembler ist und die Frage hierzu passte bitte ich um Entschuldigung für dieses Monster hier - ausgerechnet im Assembler-Forum. Die Assemblerteile sollten sich ja auch wieder verwenden lassen.
Beispiel wie das verwendet wird kommt weiter unten.
// CpuId.h: interface for the CCpuId class.
//
// Copyright (c) 1998 by Bill Oatman.
// All rights reserved.
// http://www.netacc.net/~waterbry/cpuid/cpuid.htm
//
// Basic CPU and speed detection routines
// Copyright (c) 1995, Intel Corporation. All rights reserved.
//////////////////////////////////////////////////////////////////////
#ifndef _CPUID_H
#define _CPUID_H
//nicht MFC: Anstelle der CString einfach string der STL oder char[] verwenden.
class CCpuId
{
public:
static void GetCpuSpeed(CString *CpuSpeed);
static void GetCpuId(CString *CpuIdStr);
CCpuId();
virtual ~CCpuId();
};
#endif //_CPUID_H
Die Implementierung:
// CpuId.cpp: implementation of the CCpuId class.
//
// Copyright (c) 1998 by Bill Oatman.
// All rights reserved.
// http://www.netacc.net/~waterbry/cpuid/cpuid.htm
//
// Basic CPU and speed detection routines
// Copyright (c) 1995, Intel Corporation. All rights reserved.
//////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "CpuIdTest.h"
#include "CpuId.h"
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif
typedef unsigned short ushort;
typedef unsigned long ulong;
#define CPU_ID __asm _emit 0x0f __asm _emit 0xa2
// CPUID instruction
#define RDTSC _asm _emit 0x0f _asm _emit 0x31
// RDTSC instruction
// CONSTANT DEFINITIONS ////////////////////////////////////////
#define CLONE_MASK 0x8000 // Mask to be 'OR'ed with proc-
#define MAXCLOCKS 150 // Maximum number of cycles per
// BSF instruction
// ACCURACY AFFECTING CONSTANTS ////////////////////////////
#define ITERATIONS 4000 // Number of times to repeat BSF
// instruction in samplings.
// Initially set to 4000.
#define MAX_TRIES 20 // Maximum number of samplings
// to allow before giving up
// and returning current
// average. Initially set to
// 20.
#define TOLERANCE 1 // Number of MHz to allow
// samplings to deviate from
// average of samplings.
// Initially set to 2.
#define SAMPLINGS 10 // Number of BSF sequence
// samplings to make.
// Initially set to 10.
#define ROUND_THRESHOLD 6
struct FREQ_INFO
{
unsigned long in_cycles; // Internal clock cycles during
// test
unsigned long ex_ticks; // Microseconds elapsed during
// test
unsigned long raw_freq; // Raw frequency of CPU in MHz
unsigned long norm_freq; // Normalized frequency of CPU
// in MHz.
};
// Number of cycles needed to execute a single BSF instruction.
// Note that processors below i386(tm) are not supported.
static ulong processor_cycles[] = {
00, 00, 00, 115, 47, 43,
38, 38, 38, 38, 38, 38,
};
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
CCpuId::CCpuId()
{
}
CCpuId::~CCpuId()
{
}
/***************************************************************
* check_clone()
*
* Inputs: none
*
* Returns:
* 1 if processor is clone (limited detection ability)
* 0 otherwise
***************************************************************/
static bool check_clone()
{
short cpu_type=0;
_asm
{
MOV AX,5555h // Check to make sure this
XOR DX,DX // is a 32-bit processor
MOV CX,2h
DIV CX // Perform Division
CLC
JNZ no_clone
JMP clone
no_clone: STC
clone: PUSHF
POP AX // Get the flags
AND AL,1
XOR AL,1 // AL=0 is probably Intel,
// AL=1 is a Clone
MOV cpu_type, ax
}
cpu_type = cpu_type & 0x0001;
if(cpu_type)
return TRUE;
else
return FALSE;
}
/***************************************************************
* check_IDProc()
*
* Inputs: none
*
* Returns:
* CPU Family (i.e. 4 if Intel 486, 5 if Pentium(R) Processor)
*
***************************************************************/
static WORD check_IDProc() {
int i=0;
WORD cpu_type=0xffff;
BYTE stepping=0;
BYTE model=0;
BYTE vendor_id[]="------------";
BYTE intel_id[]="GenuineIntel";
__asm {
xor eax, eax
CPU_ID
mov dword ptr vendor_id, ebx
mov dword ptr vendor_id[+4], edx
mov dword ptr vendor_id[+8], ecx
}
__asm {
cmp eax, 1 // Make sure 1 is valid input
// for CPUID
jl end_IDProc // If not, jump to end
xor eax, eax
inc eax
CPU_ID // Get family/model/stepping/
// features
mov stepping, al
and stepping, 0x0f //0fh
and al, 0f0h
shr al, 4
mov model, al
and eax, 0f00h
shr eax, 8 // Isolate family
and eax, 0fh
mov cpu_type, ax // Set _cpu_type with family
end_IDProc:
mov ax, cpu_type
}
return cpu_type;
} // Check_IDProc()
int wincpuidsupport()
{
int cpuid_support = 1;
_asm {
pushfd // Get original EFLAGS
pop eax
mov ecx, eax
xor eax, 200000h // Flip ID bit in EFLAGS
push eax // Save new EFLAGS value on
// stack
popfd // Replace current EFLAGS value
pushfd // Get new EFLAGS
pop eax // Store new EFLAGS in EAX
xor eax, ecx // Can not toggle ID bit,
jnz support // Processor=80486
mov cpuid_support,0 // Clear support flag
support:
}
return cpuid_support;
}
/***************************************************************
* wincpuid()
*
* Inputs: none
*
* Returns:
* 0 = Unknown
* 2 = 80286
* 3 = 80386
* 4 = 80486
* 5 = Pentium(R) Processor
* 6 = PentiumPro(R) Processor
* 7 or higher = Processor beyond the PentiumPro6(R) Processor
*
***************************************************************/
int wincpuid()
{
int cpuid;
bool clone_flag = FALSE;
if ( wincpuidsupport() ) // Determine whether CPUID
// opcode is supported
cpuid=check_IDProc();
else {
clone_flag=check_clone();
cpuid=0;
}
if (clone_flag)
cpuid = cpuid | CLONE_MASK; // Signify that a clone has been
// detected by setting MSB high
return cpuid;
}
void GetCpuString(int type, CString *str)
{
switch (type)
{
case 3:
*str = "386";
break;
case 4:
*str = "486";
break;
case 5:
*str = "Pentium(R) Processor";
break;
case 6:
*str = "PentiumPro(R) Processor";
break;
default:
str->Format("Unknown Type (%d)", type);
break;
}
}
void CCpuId::GetCpuId(CString * CpuIdStr)
{
int cpu_type;
int cpuid_support;
CString tStr;
cpu_type = wincpuid();
cpuid_support = wincpuidsupport();
GetCpuString(cpu_type, &tStr);
if ( cpu_type & CLONE_MASK )
CpuIdStr->Format("Intel Clone CPU Family : %s", (LPCTSTR)tStr);
else
if ( cpuid_support )
CpuIdStr->Format("Intel CPU Family : %s", (LPCTSTR)tStr);
else
CpuIdStr->Format("CPU Family : %s", (LPCTSTR)tStr);
}
/***************************************************************
* wincpufeatures()
*
* Inputs: none
*
* Returns:
* 0 = Processor which does not execute the CPUID instruction.
* This includes 8086, 8088, 80286, 80386, and some
* older 80486 processors.
*
* Else
* Feature Flags (refer to App Note AP-485 for description).
* This DWORD was put into EDX by the CPUID instruction.
*
* Current flag assignment is as follows:
*
* bit31..10 reserved (=0)
* bit9=1 CPU contains a local APIC (iPentium-3V)
* bit8=1 CMPXCHG8B instruction supported
* bit7=1 machine check exception supported
* bit6=0 reserved (36bit-addressing & 2MB-paging)
* bit5=1 iPentium-style MSRs supported
* bit4=1 time stamp counter TSC supported
* bit3=1 page size extensions supported
* bit2=1 I/O breakpoints supported
* bit1=1 enhanced virtual 8086 mode supported
* bit0=1 CPU contains a floating-point unit (FPU)
*
* Note: New bits will be assigned on future processors... see
* processor data books for updated information
*
***************************************************************/
DWORD wincpufeatures() {
int i=0;
DWORD cpuff=0x00000000;
BYTE vendor_id[]="------------";
BYTE intel_id[]="GenuineIntel";
if ( wincpuidsupport() ) {
_asm {
xor eax, eax // Set up for CPUID instruction
CPU_ID // Get and save vendor ID
mov dword ptr vendor_id, ebx
mov dword ptr vendor_id[+4], edx
mov dword ptr vendor_id[+8], ecx
}
_asm {
cmp eax, 1 // Make sure 1 is valid input
// for CPUID
jl end_cpuff // If not, jump to end
xor eax, eax
inc eax
CPU_ID // Get family/model/stepping/
// features
mov cpuff, edx
end_cpuff:
mov eax, cpuff
}
}
return cpuff;
}
static void GetRDTSCCpuSpeed(struct FREQ_INFO *cpu_speed)
{
LARGE_INTEGER t0,t1; // Variables for High-
// Resolution Performance
// Counter reads
ulong freq =0; // Most current frequ. calculation
ulong freq2 =0; // 2nd most current frequ. calc.
ulong freq3 =0; // 3rd most current frequ. calc.
ulong total; // Sum of previous three frequency
// calculations
int tries=0; // Number of times a calculation has
// been made on this call to
// cpuspeed
ulong total_cycles=0, cycles; // Clock cycles elapsed
// during test
ulong stamp0, stamp1; // Time Stamp Variable
// for beginning and end
// of test
ulong total_ticks=0, ticks; // Microseconds elapsed
// during test
LARGE_INTEGER count_freq; // High Resolution
// Performance Counter
// frequency
#ifdef WIN32
int iPriority;
HANDLE hThread = GetCurrentThread();
#endif // WIN32;
memset(cpu_speed, 0x00, sizeof(cpu_speed));
if ( !QueryPerformanceFrequency ( &count_freq ) )
return;
// On processors supporting the Read
// Time Stamp opcode, compare elapsed
// time on the High-Resolution Counter
// with elapsed cycles on the Time
// Stamp Register.
do { // This do loop runs up to 20 times or
// until the average of the previous
// three calculated frequencies is
// within 1 MHz of each of the
// individual calculated frequencies.
// This resampling increases the
// accuracy of the results since
// outside factors could affect this
// calculation
tries++; // Increment number of times sampled
// on this call to cpuspeed
freq3 = freq2; // Shift frequencies back to make
freq2 = freq; // room for new frequency
// measurement
QueryPerformanceCounter(&t0);
// Get high-resolution performance
// counter time
t1.LowPart = t0.LowPart; // Set Initial time
t1.HighPart = t0.HighPart;
#ifdef WIN32
iPriority = GetThreadPriority(hThread);
if ( iPriority != THREAD_PRIORITY_ERROR_RETURN )
{
SetThreadPriority(hThread, THREAD_PRIORITY_TIME_CRITICAL);
}
#endif // WIN32
while ( (ulong)t1.LowPart - (ulong)t0.LowPart<50) {
// Loop until 50 ticks have
// passed since last read of hi-
// res counter. This accounts for
// overhead later.
QueryPerformanceCounter(&t1);
RDTSC; // Read Time Stamp
_asm {
MOV stamp0, EAX
}
}
t0.LowPart = t1.LowPart; // Reset Initial
t0.HighPart = t1.HighPart; // Time
while ((ulong)t1.LowPart-(ulong)t0.LowPart<1000 ) {
// Loop until 1000 ticks have
// passed since last read of hi-
// res counter. This allows for
// elapsed time for sampling.
QueryPerformanceCounter(&t1);
RDTSC; // Read Time Stamp
__asm {
MOV stamp1, EAX
}
}
#ifdef WIN32
// Reset priority
if ( iPriority != THREAD_PRIORITY_ERROR_RETURN )
{
SetThreadPriority(hThread, iPriority);
}
#endif // WIN32
cycles = stamp1 - stamp0; // Number of internal
// clock cycles is
// difference between
// two time stamp
// readings.
ticks = (ulong) t1.LowPart - (ulong) t0.LowPart;
// Number of external ticks is
// difference between two
// hi-res counter reads.
// Note that some seemingly arbitrary mulitplies and
// divides are done below. This is to maintain a
// high level of precision without truncating the
// most significant data. According to what value
// ITERATIIONS is set to, these multiplies and
// divides might need to be shifted for optimal
// precision.
ticks = ticks * 100000;
// Convert ticks to hundred
// thousandths of a tick
ticks = ticks / ( count_freq.LowPart/10 );
// Hundred Thousandths of a
// Ticks / ( 10 ticks/second )
// = microseconds (us)
total_ticks += ticks;
total_cycles += cycles;
if ( ticks%count_freq.LowPart > count_freq.LowPart/2 )
ticks++; // Round up if necessary
freq = cycles/ticks; // Cycles / us = MHz
if ( cycles%ticks > ticks/2 )
freq++; // Round up if necessary
total = ( freq + freq2 + freq3 );
// Total last three frequency
// calculations
} while ( (tries < 3 ) ||
(tries < 20)&&
((abs(3 * freq -total) > 3*TOLERANCE )||
(abs(3 * freq2-total) > 3*TOLERANCE )||
(abs(3 * freq3-total) > 3*TOLERANCE )));
// Compare last three calculations to
// average of last three calculations.
// Try one more significant digit.
freq3 = ( total_cycles * 10 ) / total_ticks;
freq2 = ( total_cycles * 100 ) / total_ticks;
if ( freq2 - (freq3 * 10) >= ROUND_THRESHOLD )
freq3++;
cpu_speed->raw_freq = total_cycles / total_ticks;
cpu_speed->norm_freq = cpu_speed->raw_freq;
freq = cpu_speed->raw_freq * 10;
if( (freq3 - freq) >= ROUND_THRESHOLD )
cpu_speed->norm_freq++;
cpu_speed->ex_ticks = total_ticks;
cpu_speed->in_cycles = total_cycles;
return;
}
int GetCmosTick(void)
{
int tick = 0;
// __asm mov ah, 02h
// __asm int 1Ah
// __asm mov al, dh
// __asm and ax, 000Fh
__asm xor ax, ax
__asm out 070h, al
__asm xor ax, ax
__asm in al, 071h
// _outp( 0x70, offset );
// base = _inp( 0x71 );
// value returned in ax by function
__asm mov word ptr tick, ax
return tick;
}
//***************************************************************
//
// Function: cpuTimeStamp
//
// Returns the pentium cpu time stamp in 2 32 bit unsigned longs
//
// Notes: maintains a flag to make sure the cpu supports the RDTSC instruction. There is
// the overhead of checking the cpu the first time afterwhich the time consumed in
// checking the flag is very minimal. You could adjust the count but then you would
// have to do 64bit math. ugh.
//
//***************************************************************
unsigned long cpuTimeStamp(unsigned long *hi, unsigned long *low)
{
unsigned long ulHi = 0L;
unsigned long ulLow = 0L;
__asm {
;RDTSC
_emit 0Fh
_emit 31h
mov ulLow, eax
mov ulHi, edx
}
*hi = ulHi;
*low = ulLow;
return ulLow;
}
//***************************************************************
//
// Function: diffTime64
//
// Calculates the difference of a 64 bit time as represented by
// two 32 bit unsigned longs
//
//***************************************************************
unsigned long diffTime64(unsigned long t1Hi, unsigned long t1Low,
unsigned long t2Hi, unsigned long t2Low,
unsigned long *tHi, unsigned long *tLow )
{
unsigned long xl, xh;
/*
*tHi = t2Hi - t1Hi;
if( t1Low > t2Low )
{
*tLow = t1Low - t2Low;
*tLow = ULONG_MAX - *tLow;
*tHi -= 1;
} else {
*tLow = t2Low - t1Low;
}
*/
__asm {
mov eax, t2Low
mov ebx, t1Low
sub eax, ebx
mov xl, eax
mov eax, t2Hi
mov ebx, t1Hi
sbb eax, ebx
mov xh, eax
}
*tLow = xl;
*tHi = xh;
return *tLow;
}
#define ABS_TICK(a,b) (b<a)?b+10-a:b-a
static void GetCmosCpuSpeed(struct FREQ_INFO *cpu_speed)
{
unsigned long t1Low, t1High, t2Low, t2High, tResLow, tResHigh;
int timeStart, timeStop, lapseTime;
unsigned long temp;
unsigned long temp1;
unsigned long cpuSpeed = 0l;
#ifdef WIN32
HANDLE hThread = GetCurrentThread();
int iPriority;
#endif // WIN32
memset(cpu_speed, 0x00, sizeof(cpu_speed));
// This loop waits for the next tick
// so that we begin speed test on a tick edge
#ifdef WIN32
iPriority = GetThreadPriority(hThread);
if ( iPriority != THREAD_PRIORITY_ERROR_RETURN )
{
SetThreadPriority(hThread, iPriority+1);
}
#endif // WIN32
timeStart = GetCmosTick();
for(;;)
{
timeStop = GetCmosTick();
if ( ABS_TICK(timeStart,timeStop) > 0 )
{
cpuTimeStamp(&t1High, &t1Low);
break;
}
}
timeStart = timeStop;
for(;;)
{
timeStop = GetCmosTick();
if ( ABS_TICK(timeStart,timeStop) > 0 )
{
cpuTimeStamp(&t2High, &t2Low);
break;
}
}
#ifdef WIN32
// Set thread priority back.
if ( iPriority != THREAD_PRIORITY_ERROR_RETURN )
{
SetThreadPriority(hThread, iPriority);
}
#endif // WIN32
diffTime64(t1High, t1Low, t2High, t2Low, &tResHigh, &tResLow );
lapseTime = ABS_TICK(timeStart,timeStop);
cpuSpeed = tResLow; ///lapseTime;
cpu_speed->in_cycles = tResLow; // Cycles count since we in this routine
//round to nearest digit
temp = cpuSpeed/1000000;
temp1 = cpuSpeed/100000;
temp = temp * 10; // realign with last digit = zero
cpuSpeed = cpuSpeed/1000000; // cpuSpeed/1000000;
cpu_speed->raw_freq = cpuSpeed;
if( (temp1 - temp) >= ROUND_THRESHOLD )
cpuSpeed++;
cpu_speed->norm_freq = cpuSpeed;
cpu_speed->ex_ticks = (timeStop - timeStart) * 1000000;
return;
}
static void GetBSFCpuSpeed(ulong cycles, struct FREQ_INFO *cpu_speed)
{
// If processor does not support time
// stamp reading, but is at least a
// 386 or above, utilize method of
// timing a loop of BSF instructions
// which take a known number of cycles
// to run on i386(tm), i486(tm), and
// Pentium(R) processors.
LARGE_INTEGER t0,t1; // Variables for High-
// Resolution Performance
// Counter reads
ulong freq =0; // Most current frequ. calculation
ulong ticks; // Microseconds elapsed
// during test
LARGE_INTEGER count_freq; // High Resolution
// Performance Counter
// frequency
int i; // Temporary Variable
ulong current = 0; // Variable to store time
// elapsed during loop of
// of BSF instructions
ulong lowest = ULONG_MAX; // Since algorithm finds
// the lowest value out of
// a set of samplings,
// this variable is set
// intially to the max
// unsigned long value).
// This guarantees that
// the initialized value
// is not later used as
// the least time through
// the loop.
memset(cpu_speed, 0x00, sizeof(cpu_speed));
if ( !QueryPerformanceFrequency ( &count_freq ) )
return;
for ( i = 0; i < SAMPLINGS; i++ ) { // Sample Ten times. Can
// be increased or
// decreased depending
// on accuracy vs. time
// requirements
QueryPerformanceCounter(&t0); // Get start time
_asm
{
mov eax, 80000000h
mov bx, ITERATIONS
// Number of consecutive BSF
// instructions to execute.
// Set identical to
// nIterations constant in
// speed.h
loop1: bsf ecx,eax
dec bx
jnz loop1
}
QueryPerformanceCounter(&t1); // Get end time
current = (ulong) t1.LowPart - (ulong) t0.LowPart;
// Number of external ticks is
// difference between two
// hi-res counter reads.
if ( current < lowest ) // Take lowest elapsed
lowest = current; // time to account
} // for some samplings
// being interrupted
// by other operations
ticks = lowest;
// Note that some seemingly arbitrary mulitplies and
// divides are done below. This is to maintain a
// high level of precision without truncating the
// most significant data. According to what value
// ITERATIIONS is set to, these multiplies and
// divides might need to be shifted for optimal
// precision.
ticks = ticks * 100000;
// Convert ticks to hundred
// thousandths of a tick
ticks = ticks / ( count_freq.LowPart/10 );
// Hundred Thousandths of a
// Ticks / ( 10 ticks/second )
// = microseconds (us)
if ( ticks%count_freq.LowPart > count_freq.LowPart/2 )
ticks++; // Round up if necessary
freq = cycles/ticks; // Cycles / us = MHz
cpu_speed->raw_freq = freq;
if ( cycles%ticks > ticks/2 )
freq++; // Round up if necessary
cpu_speed->in_cycles = cycles; // Return variable structure
cpu_speed->ex_ticks = ticks; // determined by one of
cpu_speed->norm_freq = freq;
return;
}
/***************************************************************
* CpuSpeed() -- Return the raw clock rate of the host CPU.
*
* Inputs:
* clocks: 0: Use default value for number of cycles
* per BSF instruction.
* -1: Use CMos timer to get cpu speed.
* Positive Integer: Use clocks value for number
* of cycles per BSF instruction.
*
* Returns:
* If error then return all zeroes in FREQ_INFO structure
* Else return FREQ_INFO structure containing calculated
* clock frequency, normalized clock frequency, number of
* clock cycles during test sampling, and the number of
* microseconds elapsed during the sampling.
***************************************************************/
struct FREQ_INFO wincpuspeed(int clocks)
{
ulong cycles; // Clock cycles elapsed
// during test
ushort processor = wincpuid(); // Family of processor
DWORD features = wincpufeatures(); // Features of Processor
int manual=0; // Specifies whether the user
// manually entered the number of
// cycles for the BSF instruction.
struct FREQ_INFO cpu_speed; // Return structure for
// cpuspeed
memset(&cpu_speed, 0x00, sizeof(cpu_speed));
if ( processor & CLONE_MASK )
return cpu_speed;
// Check for manual BSF instruction clock count
if (clocks <= 0) {
cycles = ITERATIONS * processor_cycles[processor];
}
else if (0 < clocks && clocks <= MAXCLOCKS) {
cycles = ITERATIONS * clocks;
manual = 1; // Toggle manual control flag.
// Note that this mode will not
// work properly with processors
// which can process multiple
// BSF instructions at a time.
// For example, manual mode
// will not work on a
// PentiumPro(R)
}
if ( ( features&0x00000010 ) && !(manual) ) {
// On processors supporting the Read
// Time Stamp opcode, compare elapsed
// time on the High-Resolution Counter
// with elapsed cycles on the Time
// Stamp Register.
if ( clocks == 0 )
GetRDTSCCpuSpeed(&cpu_speed);
else
GetCmosCpuSpeed(&cpu_speed);
}
else if ( processor >= 3 ) {
GetBSFCpuSpeed(cycles, &cpu_speed);
}
return cpu_speed;
}
void CCpuId::GetCpuSpeed(CString * CpuSpeed)
{
struct FREQ_INFO cpu_speed;
cpu_speed = wincpuspeed(0);
CpuSpeed->Format("%luMHz", cpu_speed.norm_freq);
}
Beispiel:
//Test.cpp
#include "stdafx.h"
#include "cpuid.h"
#include <iostream>
using namespace std;
int main(int argc, char **argv()
{
CString m_cpuId, m_cpuSpeed; //Anstelle von CString geht auch string der STL
CCpuId::GetCpuId(&m_cpuId); //oder einfach nur ein char[] Buffer
CCpuId::GetCpuSpeed(&m_cpuSpeed);
cout << "CPU-Speed: " << m_cpuSpeed << endl;
cout << "CPU-Type: " << m_cpuId << endl;
}
Gruss René
PS: Hoffentlich werde ich jetzt nicht gesteinigt...