Pull to refresh
103
0
Евгений Гречников @grechnik

User

Send message
Я же писал: это значит, что memcpy (как и прочие функции) не обязана делать явных проверок своих аргументов. Если попросить memcpy скопировать один байт из одного мусорного указателя в другой, она может упасть (в случае NULL или совсем невалидных указателей) или действительно скопировать один байт, последствия чего в случае некорректного dst-указателя непредсказуемы.
n=0 — корректное значение длины, а ваша версия memcpy на нём сломается даже при корректных значениях указателей. Попробуйте ещё раз.
Более того, указатель на конец массива — это валидный указатель, поэтому memcpy вообще не имеет права разыменовывать переданные ей указатели при n=0.
Код типа
class MyContainer {
  // если контейнер пуст, то указатель на данные держим нулевым
  size_t Length = 0;
  int* Data = nullptr;
  ...
  void Append(const MyContainer& other) {
    Realloc(Length + other.Length);
    memcpy(Data + Length, other.Data, other.Length * sizeof(int));
    Length += other.Length;
  }
  ...
}
довольно типичен. Он работает на всех платформах, он не был сломан до того, как в gcc закоммитили изменение, его не может сломать корректная реализация memcpy, он не сломан при использовании компилятора, думающего о программистах, типа VC2015.
UB — это не какие-то законы природы, данные нам свыше. UB означает всего лишь «конструкции, которые стандарт пометил словами undefined behaviour». Часть из них действительно отражают то, как устроен компьютерный мир — в неинициализированной переменной может оказаться всё, что угодно, а если она размещена в регистре, то и Itanium-ный Not-a-Thing с исключением при чтении; аналогично при выходе за границы массива. Часть из них отражают законы физики Марса, и джинна выпустили из бутылки, когда фразу «это UB», после которой забыли написать «потому что на Марсе вот так» (signed overflow на системах со странными представлениями отрицательных чисел), начали интерпретировать «поэтому на Земле мы вам делать так тоже запретим» («ну если ваш код вдруг попадёт на Марс, он же уже сломан!»). А часть — просто неудачные формулировки, и интерпретировать фразу со смыслом «memcpy не обязана делать явных проверок своих аргументов на NULL» как «memcpy нельзя передавать NULL даже при копировании нуля байт» — расписка в бессилии сделать что-то приличными средствами.
Вот только не во всякой программе оптимизации кодогенерации могут дать «5-7% каждый год лет 10», а заменять пузырьковую сортировку быстрой компиляторы не умеют. Поэтому GCC и Clang… ломают код. Не, ну правда, "NULL как аргумент memcpy/memmove — это UB, поэтому если указатель засветился в вызове memcpy/memmove, мы выкинем все проверки с ним и весь код обработки NULL, а если вы делали memcpy(NULL, NULL, 0) — мы сломали весь ваш код, но вы сами виноваты" — больше похоже на троллинг, чем на приличную оптимизацию. (Разумеется, в чистом виде memcpy(NULL, NULL, 0) не встречается, но memcpy(p, q, len) без дополнительных проверок, где при нулевом len p или q могут быть нулями, — очень даже.)
… в которой мимоходом «доказываются» неверные утверждения.
Я писал конкретно про placement new и автоматический вызов деструктора. Это всё-таки пример, заворачивание в какую-нибудь параметризованную конструкцию дало бы лишь на одну конструкцию, в которую нужно вникать при чтении, больше.
Майерс отлично прокомментировал это свойство программистов на С++ в своём докладе Why C++ Sails When the Vasa Sank.
Кстати, на Хабре была статья с основными тезисами этого доклада: https://habrahabr.ru/company/infopulse/blog/227529/.
Тьфу, сконцентировался на deleter и забыл собственно placement new:
#include <memory>
#include <type_traits>
#include <iostream>

class MyClass
{
public:
	MyClass() { std::cout << "constructor\n"; }
	~MyClass() { std::cout << "destructor\n"; }
	void DoSomething() { std::cout << "doing something\n"; }
};

struct DeleterCallingDestructor
{
	template<typename T>
	void operator()(T* ptr) const { ptr->T::~T(); }
};

int main()
{
	std::aligned_storage<sizeof(MyClass), alignof(MyClass)>::type storage;
	std::unique_ptr<MyClass, DeleterCallingDestructor> ptr(new(&storage) MyClass);
	ptr->DoSomething();
	return 0;
}
деструктор придётся вызывать вручную, а это легко забыть сделать
Но зачем? Можно просто подсунуть unique_ptr правильный deleter:
#include <memory>
#include <type_traits>
#include <iostream>

class MyClass
{
public:
	MyClass() { std::cout << "constructor\n"; }
	~MyClass() { std::cout << "destructor\n"; }
	void DoSomething() { std::cout << "doing something\n"; }
};

struct DeleterCallingDestructor
{
	template<typename T>
	void operator()(T* ptr) const { ptr->T::~T(); }
};

int main()
{
	std::aligned_storage<sizeof(MyClass), alignof(MyClass)> storage;
	std::unique_ptr<MyClass, DeleterCallingDestructor> ptr(new MyClass);
	ptr->DoSomething();
	return 0;
}
Упражнение на закрепление материала: https://projecteuler.net/problem=558.
Если линковщик видит два файла, в которых определена одна и та же функция, то действительно будет ошибка линковки. Фокус в том, чтобы линковщик не пытался смотреть на второй из них.
Выбор файлов для линковки схематично выглядит так:
— берём все obj-файлы, явно заданные в командной строке (при компиляции из IDE это файлы главного проекта, но не файлы в проектах-зависимостях);
— пока есть неразрешённые внешние символы, ищем их в lib-библиотеках и добавляем obj-файлы из lib-библиотек.
При нормальной компиляции линковщик ищет точку входа, находит её в libcmt.lib:exe_main.obj, видит зависимость от __telemetry_main_invoke_trigger и прочих, находит их в libvcruntime.lib:telemetry.obj, подключает telemetry.obj.
Если же явно реализовать функции в своём коде, то, увидев зависимость от __telemetry_main_invoke_trigger и прочих, линковщик обнаружит, что он уже знает реализации этих функций и в telemetry.obj просто не полезет.
Нюанс 1: должны быть реализованы действительно все функции из telemetry.obj, которые может вызвать внешний код. Если, условно, в следующем обновлении функция printf будет вызывать какой-нибудь __telemetry_printf_trigger, получится ошибка линковки из-за двойного определения.
Нюанс 2: если явная реализация находится во вспомогательной библиотеке, то какая именно реализация подхватится, может зависеть от порядка перечисления библиотек в командной строке. Причём навскидку я не в курсе, в каком порядке обрабатываются встроенные библиотеки и считается ли libvcruntime.lib встроенной.
У меня в анамнезе слишком много копания в коде, чтобы думать о clean-room, и слишком много программирования, чтобы руками заполнять таблицу, которую может сделать скриптик на десяток строчек кода. Но ваш подход тоже заслуживает внимания.
Тут выше назвали одну конкретную игру, Kami no Rhapsody, это оттуда. Движок версии 4.46.
Да. В движке обработчики команд довольно любезно начинают с того, что выставляют размер команды типа mov dword ptr [esi+ecx*4+5D804h], 5, так что
примерно такой скрипт IDA
auto a,b,aprev,anext,s,f;
f=fopen("c:\\temp\\eushully_vm_cmdsize.txt","w");
for (b=0x415303;b!=BADADDR;b=NextHead(b,0x416713)){
  // установка обработчиков: mov dword ptr [esi+0xA4F24+i*4], offset cmd_i
  if (GetOpType(b,0)!=4) continue;
  fprintf(f,"0x%X\t",(GetOperandValue(b,0) - 0xA4F24)/4);
  s=GetOperandValue(b,1);
  aprev=BADADDR;
  for (a=s;a!=BADADDR;a=anext){
    anext=NextHead(a,BADADDR);
    if(!isCode(GetFlags(a))){fprintf(f,"[not a code at %X]\n",a);break;}
    if (GetOpType(a,0) == 4 && GetOperandValue(a,0) == 0x5D804) {
      if (GetOpType(a,1) == 5)fprintf(f,"%d\n",GetOperandValue(a,1));
      else if (GetOpType(a,1) == 1 &&
        GetMnem(aprev) == "mov" &&
        GetOpType(aprev,0) == 1 &&
        GetOperandValue(aprev,0) == GetOperandValue(a,1) &&
        GetOpType(aprev,1) == 5)fprintf(f,"%d\n",GetOperandValue(aprev,1));
      else fprintf(f,"[unknown write type at %x]\n",a);
      break;
    }
    // если обнаружили ветвление, на всякий случай выходим
    if(Rfirst(a)!=anext || Rnext(a,anext)!=BADADDR){a=BADADDR;break;}
    aprev=a;
  }
  if (a==BADADDR) fprintf(f,"[unresolved: %x]\n",s);
}
fclose(f);


заполняет таблицу, за исключением нескольких сложных случаев, которые уже можно добить руками.
Вы меня заинтриговали, скачал, посмотрел. А что там принципиально сложного? Ну, помимо очевидного замечания, что при количестве команд в несколько сотен выяснение всех деталей того, что делает каждая, потребует некоторой усидчивости?
Таблицы смещений в скриптах в количестве трёх штук — просто адреса команд с опкодами 0x71, 3, 0x8F. Первая — список возможных сообщений для хранения флагов прочитанности/непрочитанности, вторая — список возможных внешних вызовов из скрипта, третья — список возможных вызовов процедур. Подозреваю, что так сделано для большей стабильности сейвов: когда в сейвах стек вызовов хранится как массив индексов в отдельной таблице, а не прямо адресами внутри файла, это не будет плыть при каждом изменении скриптов.
Для правки текстов кучу смещений изменять не нужно, достаточно дописывать исправленные тексты в конец файла и править только одно смещение — собственно адрес текста.
Примитивный дизассемблер
from __future__ import print_function
import io, sys, struct
#table generated automatically, minor mistakes are possible
cmdsizes = (
0,1,1,3,3,1,5,3,3,1,5,23,1,9,25,3,	#0x0
9,19,3,9,1,11,5,5,0,0,0,0,0,0,17,25,	#0x10
13,5,5,5,5,7,9,9,9,0,9,11,11,25,11,9,	#0x20
11,9,21,13,25,23,7,23,25,0,0,0,0,0,0,0,	#0x30
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,	#0x40
7,7,7,7,7,5,7,7,7,7,7,7,7,7,7,7,	#0x50
5,7,7,5,5,5,7,7,7,7,7,7,5,1,5,3,	#0x60
11,3,3,21,3,3,3,3,3,7,7,5,1,5,3,3,	#0x70
3,3,11,7,3,1,3,1,3,9,13,3,3,5,3,3,	#0x80
15,3,5,1,1,5,1,11,0,0,0,0,0,0,0,0,	#0x90
7,1,5,5,0,0,0,0,0,0,5,5,19,1,1,1,	#0xa0
3,3,5,1,5,3,3,3,1,3,3,3,3,3,3,3,	#0xb0
3,1,5,3,3,5,5,5,3,1,1,3,5,1,7,1,	#0xc0
3,1,3,1,9,3,13,3,5,1,13,0,0,0,0,0,	#0xd0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,	#0xe0
0,0,0,0,0,0,0,0,0,0,1,5,1,5,3,1,	#0xf0
1,1,7,3,1,3,3,5,3,5,5,5,5,3,5,3,	#0x100
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,	#0x110
0,0,0,0,0,0,0,0,0,0,0,0,11,15,17,9,	#0x120
3,3,3,5,7,5,5,3,5,7,13,15,3,7,5,7,	#0x130
9,3,3,1,5,3,3,13,3,3,15,3,5,13,0,0,	#0x140
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,	#0x150
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,	#0x160
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,	#0x170
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,	#0x180
5,5,5,7,7,7,7,3,7,1,3,1,1,5,5,5,	#0x190
19,5,3,3,5,3,5,3,1,3,3,5,7,1,7,7,	#0x1a0
7,3,3,1,1,3,3,3,5,5,5,3,1,3,5,1,	#0x1b0
3,7,5,5,3,9,5,3,5,7,3,3,3,5,3,3,	#0x1c0
7,11,5,11,9,1,5,5,7,5,0,0,0,0,0,0,	#0x1d0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,	#0x1e0
0,0,0,0,1,1,1,5,9,7,3,17,3,9,11,9,	#0x1f0
3,3,11,9,9,13,15,17,7,11,3,15,1,3,1,7,	#0x200
3,3,5,7,5,5,5,9,9,9,9,3,1,5,13,15,	#0x210
13,9,5,17,1,5,11,13,11,11,7,9,7,11,13,11,	#0x220
3,9,9,11,11,11,9,5,3,13,5,15,1,1,5,5,	#0x230
9,11,5,1,1,5,5,3,3,7,7,5,5,25,3,21,	#0x240
21,25,3,5,11,13,11,11,5,1,3,3,17,7,11,9,	#0x250
9,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,	#0x260
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,	#0x270
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,	#0x280
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,	#0x290
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,	#0x2a0
0,0,0,0,0,0,0,0,0,0,0,0,23,3,3,7,	#0x2b0
7,3,13,5,1,5,5,9,9,7,3,3,3,3,3,3,	#0x2c0
7,7,7,7,7,5,5,5,7,5,17,3,3,5,5,7,	#0x2d0
7,7,7,7,7,3,5,5,3,3,3,3,5,3,3,23,	#0x2e0
19,15,13,13,7,9,3,3,5,15,3,3,11,13,0,0,	#0x2f0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,	#0x300
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,	#0x310
21,7,9,11,1,5,9,3,7,5,3,1,13,5,23,3,	#0x320
5,9,9,11,3,9,11,9,13,15,13,9,5,7,11,7	#0x330
)
# list created manually, may be incomplete
# (cmd in oplabels[i]) == (i-th operand of cmd is an offset)
oplabels = (
(0x7B,0x8C,0x8F,0xD5),
(0x7B,0x8D,0x92,0x95,0xA0,0xA2,0xA3,0xCC,0xCE,0xFB),
(0xA0,0xCE,0xD4,0x102),
(0xD4,),
(0xD6,0x90),
(0xD6,0x90),
(0x90,),
)
with io.open(sys.argv[1], 'rb') as f:
	header = struct.unpack('<4s4s6I', f.read(0x20))
	if header[0] != b'SYS3' and header[0] != b'SYS4':
		print("invalid signature")
		sys.exit(1)
	header2size = struct.unpack('<I', f.read(4))[0]
	header2 = struct.unpack('<' + str(header2size // 4 - 1) + 'I', f.read(header2size - 4))
	data = f.read()
# pass 1: prepare labels
pos = 0
maxAddr = 0
labels = set()
while pos < len(data) // 4:
	cmd = struct.unpack_from('<I', data, pos * 4)[0]
	if cmd >= len(cmdsizes) or cmdsizes[cmd] == 0:
		break
	for i in xrange((cmdsizes[cmd] - 1) // 2):
		optype, operand = struct.unpack_from('<ii', data, (pos + 1 + 2 * i) * 4)
		if i < len(oplabels) and cmd in oplabels[i] and optype == 0 and operand >= 0:
			labels.add(operand)
			maxAddr = max(maxAddr, operand)
	pos += cmdsizes[cmd]
	if cmd in (1,2,5) and pos > maxAddr:
		break
# pass 2: print
print("\theader '%s', %d, %d, %d, %d, %d, %d" % header[1:])
pos = 0
unprintedLabels = labels
firstStringOffs, lastStringOffs = -1, -1
firstArrayOffs, lastArrayOffs = -1, -1
messageStarts = []
externalCalls = []
internalCalls = []
while pos < len(data) // 4:
	if pos in labels:
		print("loc_%08X:" % pos)
		unprintedLabels.remove(pos)
	cmd = struct.unpack_from('<I', data, pos * 4)[0]
	if cmd >= len(cmdsizes) or cmdsizes[cmd] == 0:
		print("\t[invalid command 0x%X]" % cmd)
		break
	if cmd == 0x71:
		messageStarts.append(pos)
	if cmd == 3:
		externalCalls.append(pos)
	if cmd == 0x8F:
		internalCalls.append(pos)
	cmdsize = cmdsizes[cmd]
	cmdargs = struct.unpack_from('<' + str(cmdsize - 1) + 'i', data, (pos + 1) * 4)
	print("\tcmd%03X" % cmd, end='')
	for i in xrange((cmdsize - 1) // 2):
		if i:
			print(",", end='')
		print(" ", end='')
		if cmdargs[i * 2] == 0:
			if i < len(oplabels) and cmd in oplabels[i] and cmdargs[i * 2 + 1] >= 0:
				print("loc_%08X" % cmdargs[i * 2 + 1], end='')
			elif cmd == 0x64 and i == 1:
				# second arg of cmd064 is offset of array in data
				offs = cmdargs[i * 2 + 1]
				if lastArrayOffs == -1:
					firstArrayOffs = offs
				elif lastArrayOffs != offs:
					print("[Warning: out-of-order array]");
				arrsize = struct.unpack_from('<I', data, offs * 4)[0]
				arr = struct.unpack_from('<' + str(arrsize) + 'i', data, (offs + 1) * 4)
				lastArrayOffs = offs + 1 + arrsize
				print('<', end='')
				for j in xrange(arrsize):
					if j:
						print(",", end='')
					print('%d' % arr[j], end='')
				print('>', end='')
			else:
				print('%d' % cmdargs[i * 2 + 1], end='')
		elif cmdargs[i * 2] == 2:
			offs = cmdargs[i * 2 + 1] * 4
			if lastStringOffs == -1:
				firstStringOffs = offs // 4
			elif lastStringOffs != offs // 4:
				print("[Warning: out-of-order string: %08X instead of %08X]" % (offs // 4, lastStringOffs), end='');
			decoded = b''
			while True:
				s = ord(data[offs]) ^ 0xFF
				if s == 0:
					break
				decoded += chr(s)
				offs += 1
			lastStringOffs = (offs + 1) // 4 + 1
			print(b'"' + decoded + b'"', end='')
		else:
			print('op%X[0x%X]' % (cmdargs[i * 2], cmdargs[i * 2 + 1]), end='')
	print()
	pos += cmdsize
	# command 5 can be a normal command with size=1 or nofollow-command depending on ???
	# commands 4,9,0x7C are actually nofollow, but codegen seems to treat them as normal ones
	if cmd in (1,2,5) and pos > maxAddr:
		break
if len(unprintedLabels):
	print("Warning: not all labels were printed");
expectedEndAddr = min(len(data) // 4, header2[1], header2[3], header2[5])
if firstArrayOffs != -1:
	if lastArrayOffs != expectedEndAddr:
		print("Warning: range [%08X,%08X) was not printed" % (lastArrayOffs, expectedEndAddr))
	expectedEndAddr = min(expectedEndAddr, firstArrayOffs)
if firstStringOffs != -1:
	if lastStringOffs != expectedEndAddr:
		print("Warning: range [%08X,%08X) was not printed" % (lastStringOffs, expectedEndAddr))
	expectedEndAddr = min(expectedEndAddr, firstStringOffs)
if pos != expectedEndAddr:
	print("Warning: range [%08X,%08X) was not printed" % (pos, expectedEndAddr))
if messageStarts != list(struct.unpack_from('<' + str(header2[0]) + 'i', data, header2[1] * 4)):
	print("Warning: unexpected messageStarts array")
if externalCalls != list(struct.unpack_from('<' + str(header2[2]) + 'i', data, header2[3] * 4)):
	print("Warning: unexpected externalCalls array")
if internalCalls != list(struct.unpack_from('<' + str(header2[4]) + 'i', data, header2[5] * 4)):
	print("Warning: unexpected internalCalls array")


В принципе, при некотором желании выдачу дизассемблера выше можно даже скомпилировать назад с помощью fasmg, если пошаманить с его макросами.
Для разнообразия, на AMD:
AMD Phenom II P820 @1.80GHz
Visual C++ 2015, /Ox
Последовательная версия:
sign: 4.81 vs 5.14
 abs: 3.61 vs 2.40
mini: 2.40 vs 12.01
maxi: 2.40 vs 12.00
minu: 2.40 vs 12.14
maxu: 2.53 vs 12.73
Хаотическая версия:
sign: 20.19 vs -0.00
 abs: 18.07 vs 0.00
mini: 0.00 vs 9.60
maxi: -0.00 vs 12.01
minu: 0.00 vs 12.01
maxu: 0.04 vs 12.01

g++ 5.2.0, -std=c++11 -O3
Последовательная версия:
sign: 9.72 vs 9.59
 abs: 7.20 vs 7.20
mini: 7.20 vs 14.50
maxi: 7.19 vs 9.88
minu: 7.20 vs 7.20
maxu: 7.20 vs 9.59
Хаотическая версия:
sign: 17.34 vs 2.56
 abs: 16.28 vs 0.02
mini: -0.00 vs 0.00
maxi: 0.00 vs 3.61
minu: 20.99 vs 8.91
maxu: -0.00 vs 3.61

Но, надо отметить, определение empty в последовательной версии некорректно: что gcc, что clang успешно сворачивают цикл с empty в константу. gcc, кстати, в неправильную константу: https://godbolt.org/g/wibi2q.
И в качестве упражнения на закрепление материала: https://projecteuler.net/problem=316.
Извините, ЧТО?
Я бы предположил, что вы спутали код загрузчика приложений (ntdll.dll/ld-linux.so) и код рантайма конкретной среды программирования, но код загрузчика приложений тоже не имеет никакого отношения к планировщику.
И не соблаговолит ли прилежный слушатель курса операционных систем и системного программирования просветить невежественную шпану типа меня, каким образом прослушивание такого курса избавляет от вопроса «что делает вызов __telemetry_main_invoke_trigger() непосредственно перед вызовом main()»?
Ха. На Windows 10 без отключения телеметрии обновлённая версия детектора показывает, что система таки ведёт все логи. Только не уточняет, куда.
Даже если мы линкуем рантайм статически, мы не можем знать, как поведёт себя логирование и телеметрия.
hello.exe из статьи — вполне готовый бинарник, на его месте может быть любая собранная программа. А logman/tracerpt могут быть запущены совершенно независимо от него и от ведома разработчика hello.exe. В том числе апдейтом Windows. В том числе не трогающем vcruntime140.dll или как она там называется.
Как минимум это занимает ощутимое место в exe-шнике.
Да, это появилось только в VS 2015.

Information

Rating
Does not participate
Location
Москва, Москва и Московская обл., Россия
Works in
Date of birth
Registered
Activity